Skip to main content

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_or(0, |d| d.subsec_micros())
139        } else {
140            0
141        };
142
143        let this = Self {
144            options,
145            stamp,
146            config,
147            scanner,
148            writer,
149            connection,
150        };
151
152        this.log("terminal::connect")?;
153        Ok(this)
154    }
155
156    /// Get the options used when opening this connection.
157    #[inline]
158    pub fn options(&self) -> &Options {
159        &self.options
160    }
161
162    /// Get both terminal input and output.
163    ///
164    /// The returned input and output objects ensure mutually exclusive access
165    /// to the terminal's input and output, respectively. Dropping them releases
166    /// access again.
167    #[inline]
168    pub fn io(&self) -> (Input<'_>, Output<'_>) {
169        (self.input(), self.output())
170    }
171
172    /// Get the terminal input.
173    ///
174    /// The returned input object ensures mutually exclusive access to the
175    /// terminal's input. Dropping the input object releases access again.
176    ///
177    /// # Panics
178    ///
179    /// If the underlying mutex has been poisoned.
180    #[inline]
181    pub fn input(&self) -> Input<'_> {
182        Input {
183            scanner: self.scanner.lock().expect("can't lock poisoned mutex"),
184        }
185    }
186
187    /// Get the terminal output.
188    ///
189    /// The returned output object ensures mutually exclusive access to the
190    /// terminal's output. Dropping the output object releases access again.
191    ///
192    /// # Panics
193    ///
194    /// If the underlying mutex has been poisoned.
195    #[inline]
196    pub fn output(&self) -> Output<'_> {
197        Output {
198            writer: self.writer.lock().expect("can't lock poisoned mutex"),
199        }
200    }
201
202    fn log(&self, message: impl AsRef<str>) -> Result<()> {
203        if !matches!(self.options.volume(), Volume::Silent) {
204            // Don't wait for output.
205            let mut writer = self
206                .writer
207                .try_lock()
208                .map_err(|_| Error::from(ErrorKind::WouldBlock))?;
209
210            write!(
211                writer,
212                "{} pid={} group={} stamp={}\r\n",
213                message.as_ref(),
214                std::process::id(),
215                self.connection.group().unwrap_or(0),
216                self.stamp
217            )?;
218            writer.flush()
219        } else {
220            Ok(())
221        }
222    }
223}
224
225impl Drop for Connection {
226    fn drop(&mut self) {
227        let _ = self.log("terminal::disconnect");
228
229        // Execute deferred commands and flush output.
230        let _ = self.writer.lock().map(|mut writer| {
231            for cmd in writer.take().into_iter().rev() {
232                let _ = write!(writer, "{}", cmd);
233            }
234            let _ = writer.flush();
235        });
236
237        // Restore terminal configuration
238        if let Some(ref cfg) = self.config {
239            let _ = cfg.write(&self.connection);
240        }
241    }
242}
243
244// -----------------------------------------------------------------------------------------------
245
246/// A terminal [`Connection`]'s input.
247///
248/// In addition to [`Read`] and [`BufRead`], terminal input also implements
249/// [`Scan`] for ingesting text and ANSI escape sequences. The implementation of
250/// all three traits uses the same, shared buffer. At the same time, it does not
251/// share any state (nor implementation) with standard I/O in Rust's standard
252/// library.
253///
254/// Reads from the terminal connection time out after a duration configurable in
255/// 0.1s increments. In that case, [`Read::read`] returns a count of 0,
256/// [`BufRead::fill_buf`] an empty slice, and [`Scan::read_token`] an error with
257/// kind [`ErrorKind::TimedOut`](std::io::ErrorKind::TimedOut). On Unix, the
258/// timeout is implemented with the terminal's [`MIN` and `TIME`
259/// parameters](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap11.html#tag_11_01_07_03)
260/// On Windows, the timeout is implemented with
261/// [`WaitForSingleObject`](https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-waitforsingleobject).
262///
263///
264/// # Scanning Tokens vs Reading Bytes
265///
266/// Despite requiring a fairly elaborate state machine, the implementation of
267/// [`read_token()`](crate::Scan::read_token) has been carefully engineered to
268/// return to the start state whenever possible. However, that is not possible
269/// when reading from the terminal connection results in an error or when a
270/// [`Token::Control`](crate::Token::Control) appears in the middle of a
271/// [`Token::Sequence`](crate::Token::Sequence). In these cases,
272/// [`in_flight()`](crate::Scan::in_flight) returns `true`.
273///
274/// It is possible to interleave reading bytes through [`Read`] and [`BufRead`]
275/// as well as tokens through [`Scan`], as long as byte-reads consume data at
276/// token granularity as well. For that reason,
277/// [`fill_buf()`](BufRead::fill_buf) and [`consume()`](BufRead::consume) are
278/// much preferred over [`read()`](Read::read) because the former two methods
279/// provide exact control over consumed bytes, whereas the latter method does
280/// not. For the same reason, byte-reads fail with
281/// [`ErrorKind::InFlight`](crate::err::ErrorKind::InFlight), if the state
282/// machine currently is in-flight.
283///
284///
285/// # Error Recovery
286///
287/// Unless the terminal connection keeps erroring, a viable error recovery
288/// strategy is to keep reading tokens. The state machine usually returns to the
289/// start state after the first error. Doing so requires reading at most 3 bytes
290/// for UTF-8 characters and, in theory, an unlimited number of bytes for
291/// pathological ANSI escape sequences.
292///
293///
294/// # Pathological Input
295///
296/// To protect against such pathological inputs, the implementation gracefully
297/// handles out-of-memory conditions, i.e., when a sequence is longer than the
298/// internal buffer size. It does *not* dynamically grow the buffer size, but
299/// instead keeps processing bytes until the sequence is complete and then
300/// returns [`ErrorKind::OutOfMemory`](crate::err::ErrorKind::OutOfMemory).
301/// However, if a sequence is much longer than the buffer size, continuing to
302/// scan it makes little sense. Hence, upon reaching a configurable limit, the
303/// state machine forcibly resets and discards any unread bytes before returning
304/// [`ErrorKind::PathologicalSequence`](crate::err::ErrorKind::PathologicalSequence).
305/// In that case, it probably is advisable to terminate the terminal connection,
306/// since a denial-of-service attack appears to be under way.
307#[derive(Debug)]
308pub struct Input<'a> {
309    pub scanner: MutexGuard<'a, Scanner<Box<dyn Read + Send>>>,
310}
311
312impl Input<'_> {
313    /// Determine whether the input has bytes buffered.
314    #[must_use = "the only reason to invoke method is to access the returned value"]
315    pub fn is_readable(&self) -> bool {
316        self.scanner.is_readable()
317    }
318}
319
320impl Scan for Input<'_> {
321    #[inline]
322    fn in_flight(&self) -> bool {
323        self.scanner.in_flight()
324    }
325
326    #[inline]
327    fn read_token(&mut self) -> Result<crate::Token<'_>> {
328        self.scanner.read_token().map_err(core::convert::Into::into)
329    }
330}
331
332impl Read for Input<'_> {
333    #[inline]
334    fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
335        let mut source = self.scanner.fill_buf()?;
336        let count = source.read(buf)?;
337        self.scanner.consume(count)?;
338        Ok(count)
339    }
340}
341
342impl BufRead for Input<'_> {
343    #[inline]
344    fn fill_buf(&mut self) -> Result<&[u8]> {
345        self.scanner.fill_buf().map_err(core::convert::Into::into)
346    }
347
348    #[inline]
349    fn consume(&mut self, amt: usize) {
350        // Don't panic...
351        let _ = self.scanner.consume(amt);
352    }
353}
354
355// -----------------------------------------------------------------------------------------------
356
357/// A terminal [`Connection`]'s output.
358///
359/// Since terminal output is buffered, actually executing commands requires
360/// flushing the output. As a convenience, [`Output::print`] and
361/// [`Output::println`] write strings and [`Output::exec`] writes individual
362/// commands, while also flushing the output on every invocation.
363#[derive(Debug)]
364pub struct Output<'a> {
365    writer: MutexGuard<'a, DeferredWriter>,
366}
367
368impl Output<'_> {
369    /// Write and flush the text.
370    #[inline]
371    #[must_use = "method returns result that may indicate an error"]
372    pub fn print<T: AsRef<str>>(&mut self, text: T) -> Result<()> {
373        self.writer.write_all(text.as_ref().as_bytes())?;
374        self.writer.flush()
375    }
376
377    /// Write and flush the text followed by carriage return and line feed.
378    #[inline]
379    #[must_use = "method returns result that may indicate an error"]
380    pub fn println<T: AsRef<str>>(&mut self, text: T) -> Result<()> {
381        self.writer.write_all(text.as_ref().as_bytes())?;
382        self.writer.write_all(b"\r\n")?;
383        self.writer.flush()
384    }
385
386    /// Execute the command.
387    ///
388    /// This method writes the display for the given command and then flushes
389    /// the terminal output.
390    #[inline]
391    #[must_use = "method returns result that may indicate an error"]
392    pub fn exec<C: Command>(&mut self, cmd: C) -> Result<()> {
393        write!(self.writer, "{}", cmd)?;
394        self.writer.flush()
395    }
396
397    /// Execute one command and defer the other.
398    ///
399    /// This method tries to write the first command to the terminal's output.
400    /// If that succeeds, it enqueues the second command for execution when the
401    /// connection is being closed and then flushes the output.
402    ///
403    /// The second command must be `'static`, so that it is alive for the
404    /// lifetime of the connection. It must be `Send`, so that connection
405    /// objects can be moved across threads. Since most commands are zero-sized
406    /// types, they trivially fulfill both requirements.
407    #[must_use = "method returns result that may indicate an error"]
408    pub fn exec_defer<C1, C2>(&mut self, cmd1: C1, cmd2: C2) -> Result<()>
409    where
410        C1: Command,
411        C2: Command + Send + 'static,
412    {
413        write!(self.writer, "{}", cmd1)?;
414        self.writer.defer(cmd2);
415        self.writer.flush()
416    }
417}
418
419impl Write for Output<'_> {
420    #[inline]
421    fn write(&mut self, buf: &[u8]) -> Result<usize> {
422        self.writer.write(buf)
423    }
424
425    #[inline]
426    fn flush(&mut self) -> Result<()> {
427        self.writer.flush()
428    }
429}