prettytty/
conn.rs

1use core::cell::RefCell;
2use std::io::{BufRead, BufWriter, Error, ErrorKind, Read, Result, Write};
3use std::sync::{Mutex, MutexGuard};
4
5use crate::opt::{Options, Volume};
6use crate::read::{DoggedReader, VerboseReader};
7use crate::scan::Scanner;
8use crate::sys::{RawConfig, RawConnection, RawOutput};
9use crate::{Command, Scan};
10
11// -----------------------------------------------------------------------------------------------
12
13/// A writer with associated deferred commands.
14#[derive(Debug)]
15struct DeferredWriter {
16    writer: BufWriter<RawOutput>,
17    deferred: RefCell<Vec<Box<dyn Command + Send>>>,
18}
19
20impl DeferredWriter {
21    /// Create a new list of deferred commands.
22    pub fn new(writer: RawOutput, options: &Options) -> Self {
23        Self {
24            writer: BufWriter::with_capacity(options.write_buffer_size(), writer),
25            deferred: RefCell::new(Vec::new()),
26        }
27    }
28
29    /// Defer the execution of the given command.
30    pub fn defer<C>(&self, cmd: C)
31    where
32        C: Command + Send + 'static,
33    {
34        self.deferred.borrow_mut().push(Box::new(cmd));
35    }
36
37    /// Take the list of commands and leave empty list behind.
38    pub fn take(&self) -> Vec<Box<dyn Command + Send>> {
39        self.deferred.take()
40    }
41}
42
43impl Write for DeferredWriter {
44    fn write(&mut self, buf: &[u8]) -> Result<usize> {
45        self.writer.write(buf)
46    }
47
48    fn flush(&mut self) -> Result<()> {
49        self.writer.flush()
50    }
51}
52
53// -----------------------------------------------------------------------------------------------
54
55/// A terminal connection providing [`Input`] and [`Output`].
56///
57/// This object owns the connection to the terminal. It provides independent,
58/// mutually exclusive, and thread-safe access to [`Input`] as well as
59/// [`Output`]. On Unix, the I/O types share the same underlying file
60/// descriptor, whereas on Windows each I/O type uses a distinct handle.
61///
62/// To facilitate reading from the terminal, this type reconfigures the
63/// terminal, at a minimum by disabling the terminal's line editing mode. Since
64/// its drop handler restores the original configuration, **an application
65/// should go out of its way to always execute this type's drop handler** before
66/// exit. Use [`drop`](std::mem::drop) to manually close a connection before it
67/// goes out of scope.
68///
69/// An application may need to make further changes, such as using the alternate
70/// screen or hiding the cursor, that also need to be undone before exit. In
71/// that case, the application can use [`Output::exec_defer`]. The method takes
72/// two [`Command`]s, executes the first command right away, and defers the
73/// second command to just before the terminal connection is closed.
74#[derive(Debug)]
75pub struct Connection {
76    options: Options,
77    stamp: u32,
78    config: Option<RawConfig>,
79    scanner: Mutex<Scanner<Box<dyn Read + Send>>>,
80    writer: Mutex<DeferredWriter>,
81    connection: RawConnection,
82}
83
84fn _assert_connection_is_send_sync() {
85    fn is_send_sync<T: Send + Sync>() {}
86    is_send_sync::<Connection>();
87}
88
89impl Connection {
90    /// Open a terminal connection with the default options.
91    pub fn open() -> Result<Self> {
92        Self::with_options(Options::default())
93    }
94
95    /// Open a terminal connection with the given options.
96    ///
97    /// If this method cannot establish a connection to the controlling
98    /// terminal, it fails with a [`ErrorKind::ConnectionRefused`] error.
99    #[allow(clippy::print_stdout)]
100    pub fn with_options(options: Options) -> Result<Self> {
101        let connection = RawConnection::open(&options)
102            .map_err(|e| Error::new(ErrorKind::ConnectionRefused, e))?;
103
104        let config = RawConfig::read(&connection)?;
105        let verbose = !matches!(options.volume(), Volume::Silent);
106        if verbose {
107            println!("terminal::config {:?}", &config);
108        }
109        let config = config.apply(&options).map_or_else(
110            || Ok::<Option<RawConfig>, Error>(None),
111            |reconfig| {
112                if verbose {
113                    println!("terminal::reconfig {:?}", &reconfig);
114                }
115                reconfig.write(&connection)?;
116                if verbose {
117                    // We need explicit carriage-return and line-feed characters
118                    // because the reconfiguration just took effect.
119                    print!("terminal::reconfigured\r\n")
120                }
121                Ok(Some(config))
122            },
123        )?;
124
125        let reader: Box<dyn Read + Send> = if matches!(options.volume(), Volume::Detailed) {
126            Box::new(VerboseReader::new(connection.input(), options.timeout()))
127        } else {
128            Box::new(DoggedReader::new(connection.input()))
129        };
130        let scanner = Mutex::new(Scanner::with_options(&options, reader));
131        let writer = Mutex::new(DeferredWriter::new(connection.output(), &options));
132        let stamp = if verbose {
133            // macOS duration has microsecond resolution only, so that's our
134            // least common denominator. If duration_since() fails, we use an
135            // obviously wrong value as stamp.
136            std::time::SystemTime::now()
137                .duration_since(std::time::SystemTime::UNIX_EPOCH)
138                .map(|d| d.subsec_micros())
139                .unwrap_or(0)
140        } else {
141            0
142        };
143
144        let this = Self {
145            options,
146            stamp,
147            config,
148            scanner,
149            writer,
150            connection,
151        };
152
153        this.log("terminal::connect")?;
154        Ok(this)
155    }
156
157    /// Get the options used when opening this connection.
158    #[inline]
159    pub fn options(&self) -> &Options {
160        &self.options
161    }
162
163    /// Get both terminal input and output.
164    ///
165    /// The returned input and output objects ensure mutually exclusive access
166    /// to the terminal's input and output, respectively. Dropping them releases
167    /// access again.
168    #[inline]
169    pub fn io(&self) -> (Input, Output) {
170        (self.input(), self.output())
171    }
172
173    /// Get the terminal input.
174    ///
175    /// The returned input object ensures mutually exclusive access to the
176    /// terminal's input. Dropping the input object releases access again.
177    ///
178    /// # Panics
179    ///
180    /// If the underlying mutex has been poisoned.
181    #[inline]
182    pub fn input(&self) -> Input {
183        Input {
184            scanner: self.scanner.lock().expect("can't lock poisoned mutex"),
185        }
186    }
187
188    /// Get the terminal output.
189    ///
190    /// The returned output object ensures mutually exclusive access to the
191    /// terminal's output. Dropping the output object releases access again.
192    ///
193    /// # Panics
194    ///
195    /// If the underlying mutex has been poisoned.
196    #[inline]
197    pub fn output(&self) -> Output {
198        Output {
199            writer: self.writer.lock().expect("can't lock poisoned mutex"),
200        }
201    }
202
203    fn log(&self, message: impl AsRef<str>) -> Result<()> {
204        if !matches!(self.options.volume(), Volume::Silent) {
205            // Don't wait for output.
206            let mut writer = self
207                .writer
208                .try_lock()
209                .map_err(|_| Error::from(ErrorKind::WouldBlock))?;
210
211            write!(
212                writer,
213                "{} pid={} group={} stamp={}\r\n",
214                message.as_ref(),
215                std::process::id(),
216                self.connection.group().unwrap_or(0),
217                self.stamp
218            )?;
219            writer.flush()
220        } else {
221            Ok(())
222        }
223    }
224}
225
226impl Drop for Connection {
227    fn drop(&mut self) {
228        let _ = self.log("terminal::disconnect");
229
230        // Execute deferred commands and flush output.
231        let _ = self.writer.lock().map(|mut writer| {
232            for cmd in writer.take().into_iter().rev() {
233                let _ = write!(writer, "{}", cmd);
234            }
235            let _ = writer.flush();
236        });
237
238        // Restore terminal configuration
239        if let Some(ref cfg) = self.config {
240            let _ = cfg.write(&self.connection);
241        }
242    }
243}
244
245// -----------------------------------------------------------------------------------------------
246
247/// A terminal [`Connection`]'s input.
248///
249/// In addition to [`Read`] and [`BufRead`], terminal input also implements
250/// [`Scan`] for ingesting text and ANSI escape sequences. The implementation of
251/// all three traits uses the same, shared buffer. At the same time, it does not
252/// share any state (nor implementation) with standard I/O in Rust's standard
253/// library.
254///
255/// Reads from the terminal connection time out after a duration configurable in
256/// 0.1s increments. In that case, [`Read::read`] returns a count of 0,
257/// [`BufRead::fill_buf`] an empty slice, and [`Scan::read_token`] an error with
258/// kind [`ErrorKind::TimedOut`](std::io::ErrorKind::TimedOut). On Unix, the
259/// timeout is implemented with the terminal's [`MIN` and `TIME`
260/// parameters](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap11.html#tag_11_01_07_03)
261/// On Windows, the timeout is implemented with
262/// [`WaitForSingleObject`](https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-waitforsingleobject).
263///
264///
265/// # Scanning Tokens vs Reading Bytes
266///
267/// Despite requiring a fairly elaborate state machine, the implementation of
268/// [`read_token()`](crate::Scan::read_token) has been carefully engineered to
269/// return to the start state whenever possible. However, that is not possible
270/// when reading from the terminal connection results in an error or when a
271/// [`Token::Control`](crate::Token::Control) appears in the middle of a
272/// [`Token::Sequence`](crate::Token::Sequence). In these cases,
273/// [`in_flight()`](crate::Scan::in_flight) returns `true`.
274///
275/// It is possible to interleave reading bytes through [`Read`] and [`BufRead`]
276/// as well as tokens through [`Scan`], as long as byte-reads consume data at
277/// token granularity as well. For that reason,
278/// [`fill_buf()`](BufRead::fill_buf) and [`consume()`](BufRead::consume) are
279/// much preferred over [`read()`](Read::read) because the former two methods
280/// provide exact control over consumed bytes, whereas the latter method does
281/// not. For the same reason, byte-reads fail with
282/// [`ErrorKind::InFlight`](crate::err::ErrorKind::InFlight), if the state
283/// machine currently is in-flight.
284///
285///
286/// # Error Recovery
287///
288/// Unless the terminal connection keeps erroring, a viable error recovery
289/// strategy is to keep reading tokens. The state machine usually returns to the
290/// start state after the first error. Doing so requires reading at most 3 bytes
291/// for UTF-8 characters and, in theory, an unlimited number of bytes for
292/// pathological ANSI escape sequences.
293///
294///
295/// # Pathological Input
296///
297/// To protect against such pathological inputs, the implementation gracefully
298/// handles out-of-memory conditions, i.e., when a sequence is longer than the
299/// internal buffer size. It does *not* dynamically grow the buffer size, but
300/// instead keeps processing bytes until the sequence is complete and then
301/// returns [`ErrorKind::OutOfMemory`](crate::err::ErrorKind::OutOfMemory).
302/// However, if a sequence is much longer than the buffer size, continuing to
303/// scan it makes little sense. Hence, upon reaching a configurable limit, the
304/// state machine forcibly resets and discards any unread bytes before returning
305/// [`ErrorKind::PathologicalSequence`](crate::err::ErrorKind::PathologicalSequence).
306/// In that case, it probably is advisable to terminate the terminal connection,
307/// since a denial-of-service attack appears to be under way.
308#[derive(Debug)]
309pub struct Input<'a> {
310    pub scanner: MutexGuard<'a, Scanner<Box<dyn Read + Send>>>,
311}
312
313impl Input<'_> {
314    /// Determine whether the input has bytes buffered.
315    #[must_use = "the only reason to invoke method is to access the returned value"]
316    pub fn is_readable(&self) -> bool {
317        self.scanner.is_readable()
318    }
319}
320
321impl Scan for Input<'_> {
322    #[inline]
323    fn in_flight(&self) -> bool {
324        self.scanner.in_flight()
325    }
326
327    #[inline]
328    fn read_token(&mut self) -> Result<crate::Token> {
329        self.scanner.read_token().map_err(core::convert::Into::into)
330    }
331}
332
333impl Read for Input<'_> {
334    #[inline]
335    fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
336        let mut source = self.scanner.fill_buf()?;
337        let count = source.read(buf)?;
338        self.scanner.consume(count)?;
339        Ok(count)
340    }
341}
342
343impl BufRead for Input<'_> {
344    #[inline]
345    fn fill_buf(&mut self) -> Result<&[u8]> {
346        self.scanner.fill_buf().map_err(core::convert::Into::into)
347    }
348
349    #[inline]
350    fn consume(&mut self, amt: usize) {
351        // Don't panic...
352        let _ = self.scanner.consume(amt);
353    }
354}
355
356// -----------------------------------------------------------------------------------------------
357
358/// A terminal [`Connection`]'s output.
359///
360/// Since terminal output is buffered, actually executing commands requires
361/// flushing the output. As a convenience, [`Output::print`] and
362/// [`Output::println`] write strings and [`Output::exec`] writes individual
363/// commands, while also flushing the output on every invocation.
364#[derive(Debug)]
365pub struct Output<'a> {
366    writer: MutexGuard<'a, DeferredWriter>,
367}
368
369impl Output<'_> {
370    /// Write and flush the text.
371    #[inline]
372    #[must_use = "method returns result that may indicate an error"]
373    pub fn print<T: AsRef<str>>(&mut self, text: T) -> Result<()> {
374        self.writer.write_all(text.as_ref().as_bytes())?;
375        self.writer.flush()
376    }
377
378    /// Write and flush the text followed by carriage return and line feed.
379    #[inline]
380    #[must_use = "method returns result that may indicate an error"]
381    pub fn println<T: AsRef<str>>(&mut self, text: T) -> Result<()> {
382        self.writer.write_all(text.as_ref().as_bytes())?;
383        self.writer.write_all(b"\r\n")?;
384        self.writer.flush()
385    }
386
387    /// Execute the command.
388    ///
389    /// This method writes the display for the given command and then flushes
390    /// the terminal output.
391    #[inline]
392    #[must_use = "method returns result that may indicate an error"]
393    pub fn exec<C: Command>(&mut self, cmd: C) -> Result<()> {
394        write!(self.writer, "{}", cmd)?;
395        self.writer.flush()
396    }
397
398    /// Execute one command and defer the other.
399    ///
400    /// This method tries to write the first command to the terminal's output.
401    /// If that succeeds, it enqueues the second command for execution when the
402    /// connection is being closed and then flushes the output.
403    ///
404    /// The second command must be `'static`, so that it is alive for the
405    /// lifetime of the connection. It must be `Send`, so that connection
406    /// objects can be moved across threads. Since most commands are zero-sized
407    /// types, they trivially fulfill both requirements.
408    #[must_use = "method returns result that may indicate an error"]
409    pub fn exec_defer<C1, C2>(&mut self, cmd1: C1, cmd2: C2) -> Result<()>
410    where
411        C1: Command,
412        C2: Command + Send + 'static,
413    {
414        write!(self.writer, "{}", cmd1)?;
415        self.writer.defer(cmd2);
416        self.writer.flush()
417    }
418}
419
420impl Write for Output<'_> {
421    #[inline]
422    fn write(&mut self, buf: &[u8]) -> Result<usize> {
423        self.writer.write(buf)
424    }
425
426    #[inline]
427    fn flush(&mut self) -> Result<()> {
428        self.writer.flush()
429    }
430}