xref: /cloud-hypervisor/vmm/src/serial_manager.rs (revision 6f8bd27cf7629733582d930519e98d19e90afb16)
1 // Copyright © 2021 Intel Corporation
2 //
3 // SPDX-License-Identifier: Apache-2.0
4 //
5 
6 use crate::config::ConsoleOutputMode;
7 use crate::device_manager::PtyPair;
8 #[cfg(target_arch = "aarch64")]
9 use devices::legacy::Pl011;
10 #[cfg(target_arch = "x86_64")]
11 use devices::legacy::Serial;
12 use libc::EFD_NONBLOCK;
13 use serial_buffer::SerialBuffer;
14 use std::fs::File;
15 use std::io::Read;
16 use std::os::unix::io::{AsRawFd, FromRawFd};
17 use std::panic::AssertUnwindSafe;
18 use std::sync::atomic::{AtomicBool, Ordering};
19 use std::sync::{Arc, Mutex};
20 use std::{io, result, thread};
21 use thiserror::Error;
22 use vmm_sys_util::eventfd::EventFd;
23 
24 #[derive(Debug, Error)]
25 pub enum Error {
26     /// Cannot clone File.
27     #[error("Error cloning File: {0}")]
28     FileClone(#[source] io::Error),
29 
30     /// Cannot create epoll context.
31     #[error("Error creating epoll context: {0}")]
32     Epoll(#[source] io::Error),
33 
34     /// Cannot handle the VM input stream.
35     #[error("Error handling VM input: {0:?}")]
36     ReadInput(#[source] io::Error),
37 
38     /// Cannot queue input to the serial device.
39     #[error("Error queuing input to the serial device: {0}")]
40     QueueInput(#[source] vmm_sys_util::errno::Error),
41 
42     /// Cannot flush output on the serial buffer.
43     #[error("Error flushing serial device's output buffer: {0}")]
44     FlushOutput(#[source] io::Error),
45 
46     /// Cannot make the file descriptor non-blocking.
47     #[error("Error making input file descriptor non-blocking: {0}")]
48     SetNonBlocking(#[source] io::Error),
49 
50     /// Cannot create EventFd.
51     #[error("Error creating EventFd: {0}")]
52     EventFd(#[source] io::Error),
53 
54     /// Cannot spawn SerialManager thread.
55     #[error("Error spawning SerialManager thread: {0}")]
56     SpawnSerialManager(#[source] io::Error),
57 }
58 pub type Result<T> = result::Result<T, Error>;
59 
60 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
61 #[repr(u64)]
62 pub enum EpollDispatch {
63     File = 0,
64     Kill = 1,
65     Unknown,
66 }
67 
68 impl From<u64> for EpollDispatch {
69     fn from(v: u64) -> Self {
70         use EpollDispatch::*;
71         match v {
72             0 => File,
73             1 => Kill,
74             _ => Unknown,
75         }
76     }
77 }
78 
79 pub struct SerialManager {
80     #[cfg(target_arch = "x86_64")]
81     serial: Arc<Mutex<Serial>>,
82     #[cfg(target_arch = "aarch64")]
83     serial: Arc<Mutex<Pl011>>,
84     epoll_file: File,
85     in_file: File,
86     kill_evt: EventFd,
87     handle: Option<thread::JoinHandle<()>>,
88     pty_write_out: Option<Arc<AtomicBool>>,
89 }
90 
91 impl SerialManager {
92     pub fn new(
93         #[cfg(target_arch = "x86_64")] serial: Arc<Mutex<Serial>>,
94         #[cfg(target_arch = "aarch64")] serial: Arc<Mutex<Pl011>>,
95         pty_pair: Option<Arc<Mutex<PtyPair>>>,
96         mode: ConsoleOutputMode,
97     ) -> Result<Option<Self>> {
98         let in_file = match mode {
99             ConsoleOutputMode::Pty => {
100                 if let Some(pty_pair) = pty_pair {
101                     pty_pair
102                         .lock()
103                         .unwrap()
104                         .main
105                         .try_clone()
106                         .map_err(Error::FileClone)?
107                 } else {
108                     return Ok(None);
109                 }
110             }
111             ConsoleOutputMode::Tty => {
112                 // If running on an interactive TTY then accept input
113                 // SAFETY: trivially safe
114                 if unsafe { libc::isatty(libc::STDIN_FILENO) == 1 } {
115                     // SAFETY: STDIN_FILENO is a valid fd
116                     let stdin_clone = unsafe { File::from_raw_fd(libc::dup(libc::STDIN_FILENO)) };
117                     // SAFETY: FFI calls with correct arguments
118                     let ret = unsafe {
119                         let mut flags = libc::fcntl(stdin_clone.as_raw_fd(), libc::F_GETFL);
120                         flags |= libc::O_NONBLOCK;
121                         libc::fcntl(stdin_clone.as_raw_fd(), libc::F_SETFL, flags)
122                     };
123 
124                     if ret < 0 {
125                         return Err(Error::SetNonBlocking(std::io::Error::last_os_error()));
126                     }
127 
128                     stdin_clone
129                 } else {
130                     return Ok(None);
131                 }
132             }
133             _ => return Ok(None),
134         };
135 
136         let epoll_fd = epoll::create(true).map_err(Error::Epoll)?;
137         let kill_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::EventFd)?;
138 
139         epoll::ctl(
140             epoll_fd,
141             epoll::ControlOptions::EPOLL_CTL_ADD,
142             kill_evt.as_raw_fd(),
143             epoll::Event::new(epoll::Events::EPOLLIN, EpollDispatch::Kill as u64),
144         )
145         .map_err(Error::Epoll)?;
146 
147         epoll::ctl(
148             epoll_fd,
149             epoll::ControlOptions::EPOLL_CTL_ADD,
150             in_file.as_raw_fd(),
151             epoll::Event::new(epoll::Events::EPOLLIN, EpollDispatch::File as u64),
152         )
153         .map_err(Error::Epoll)?;
154 
155         let mut pty_write_out = None;
156         if mode == ConsoleOutputMode::Pty {
157             let write_out = Arc::new(AtomicBool::new(false));
158             pty_write_out = Some(write_out.clone());
159             let writer = in_file.try_clone().map_err(Error::FileClone)?;
160             let buffer = SerialBuffer::new(Box::new(writer), write_out);
161             serial.as_ref().lock().unwrap().set_out(Box::new(buffer));
162         }
163 
164         // Use 'File' to enforce closing on 'epoll_fd'
165         // SAFETY: epoll_fd is valid
166         let epoll_file = unsafe { File::from_raw_fd(epoll_fd) };
167 
168         Ok(Some(SerialManager {
169             serial,
170             epoll_file,
171             in_file,
172             kill_evt,
173             handle: None,
174             pty_write_out,
175         }))
176     }
177 
178     // This function should be called when the other end of the PTY is
179     // connected. It verifies if this is the first time it's been invoked
180     // after the connection happened, and if that's the case it flushes
181     // all output from the serial to the PTY. Otherwise, it's a no-op.
182     fn trigger_pty_flush(
183         #[cfg(target_arch = "x86_64")] serial: &Arc<Mutex<Serial>>,
184         #[cfg(target_arch = "aarch64")] serial: &Arc<Mutex<Pl011>>,
185         pty_write_out: Option<&Arc<AtomicBool>>,
186     ) -> Result<()> {
187         if let Some(pty_write_out) = &pty_write_out {
188             if pty_write_out.load(Ordering::Acquire) {
189                 return Ok(());
190             }
191 
192             pty_write_out.store(true, Ordering::Release);
193 
194             serial
195                 .lock()
196                 .unwrap()
197                 .flush_output()
198                 .map_err(Error::FlushOutput)?;
199         }
200 
201         Ok(())
202     }
203 
204     pub fn start_thread(&mut self, exit_evt: EventFd) -> Result<()> {
205         // Don't allow this to be run if the handle exists
206         if self.handle.is_some() {
207             warn!("Tried to start multiple SerialManager threads, ignoring");
208             return Ok(());
209         }
210 
211         let epoll_fd = self.epoll_file.as_raw_fd();
212         let mut in_file = self.in_file.try_clone().map_err(Error::FileClone)?;
213         let serial = self.serial.clone();
214         let pty_write_out = self.pty_write_out.clone();
215 
216         // In case of PTY, we want to be able to detect a connection on the
217         // other end of the PTY. This is done by detecting there's no event
218         // triggered on the epoll, which is the reason why we want the
219         // epoll_wait() function to return after the timeout expired.
220         // In case of TTY, we don't expect to detect such behavior, which is
221         // why we can afford to block until an actual event is triggered.
222         let timeout = if pty_write_out.is_some() { 500 } else { -1 };
223 
224         let thread = thread::Builder::new()
225             .name("serial-manager".to_string())
226             .spawn(move || {
227                 std::panic::catch_unwind(AssertUnwindSafe(move || {
228                     // 3 for File, Kill, and Unknown
229                     const EPOLL_EVENTS_LEN: usize = 3;
230 
231                     let mut events =
232                         vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN];
233 
234                     loop {
235                         let num_events = match epoll::wait(epoll_fd, timeout, &mut events[..]) {
236                             Ok(res) => res,
237                             Err(e) => {
238                                 if e.kind() == io::ErrorKind::Interrupted {
239                                     // It's well defined from the epoll_wait() syscall
240                                     // documentation that the epoll loop can be interrupted
241                                     // before any of the requested events occurred or the
242                                     // timeout expired. In both those cases, epoll_wait()
243                                     // returns an error of type EINTR, but this should not
244                                     // be considered as a regular error. Instead it is more
245                                     // appropriate to retry, by calling into epoll_wait().
246                                     continue;
247                                 } else {
248                                     return Err(Error::Epoll(e));
249                                 }
250                             }
251                         };
252 
253                         if num_events == 0 {
254                             // This very specific case happens when the serial is connected
255                             // to a PTY. We know EPOLLHUP is always present when there's nothing
256                             // connected at the other end of the PTY. That's why getting no event
257                             // means we can flush the output of the serial through the PTY.
258                             Self::trigger_pty_flush(&serial, pty_write_out.as_ref())?;
259                             continue;
260                         }
261 
262                         for event in events.iter().take(num_events) {
263                             let dispatch_event: EpollDispatch = event.data.into();
264                             match dispatch_event {
265                                 EpollDispatch::Unknown => {
266                                     let event = event.data;
267                                     warn!("Unknown serial manager loop event: {}", event);
268                                 }
269                                 EpollDispatch::File => {
270                                     if event.events & libc::EPOLLIN as u32 != 0 {
271                                         let mut input = [0u8; 64];
272                                         let count =
273                                             in_file.read(&mut input).map_err(Error::ReadInput)?;
274 
275                                         // Replace "\n" with "\r" to deal with Windows SAC (#1170)
276                                         if count == 1 && input[0] == 0x0a {
277                                             input[0] = 0x0d;
278                                         }
279 
280                                         serial
281                                             .as_ref()
282                                             .lock()
283                                             .unwrap()
284                                             .queue_input_bytes(&input[..count])
285                                             .map_err(Error::QueueInput)?;
286                                     }
287                                     if event.events & libc::EPOLLHUP as u32 != 0 {
288                                         if let Some(pty_write_out) = &pty_write_out {
289                                             pty_write_out.store(false, Ordering::Release);
290                                         }
291                                         // It's really important to sleep here as this will prevent
292                                         // the current thread from consuming 100% of the CPU cycles
293                                         // when waiting for someone to connect to the PTY.
294                                         std::thread::sleep(std::time::Duration::from_millis(500));
295                                     } else {
296                                         // If the EPOLLHUP flag is not up on the associated event, we
297                                         // can assume the other end of the PTY is connected and therefore
298                                         // we can flush the output of the serial to it.
299                                         Self::trigger_pty_flush(&serial, pty_write_out.as_ref())?;
300                                     }
301                                 }
302                                 EpollDispatch::Kill => {
303                                     info!("KILL event received, stopping epoll loop");
304                                     return Ok(());
305                                 }
306                             }
307                         }
308                     }
309                 }))
310                 .map_err(|_| {
311                     error!("serial-manager thread panicked");
312                     exit_evt.write(1).ok()
313                 })
314                 .ok();
315             })
316             .map_err(Error::SpawnSerialManager)?;
317         self.handle = Some(thread);
318         Ok(())
319     }
320 }
321 
322 impl Drop for SerialManager {
323     fn drop(&mut self) {
324         self.kill_evt.write(1).ok();
325         if let Some(handle) = self.handle.take() {
326             handle.join().ok();
327         }
328     }
329 }
330