xref: /cloud-hypervisor/vmm/src/serial_manager.rs (revision 274f1aa2e738d579ffff9d4cfd7ed7c45293af31)
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::net::Shutdown;
17 use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd};
18 use std::os::unix::net::{UnixListener, UnixStream};
19 use std::panic::AssertUnwindSafe;
20 use std::path::PathBuf;
21 use std::sync::atomic::{AtomicBool, Ordering};
22 use std::sync::{Arc, Mutex};
23 use std::{io, result, thread};
24 use thiserror::Error;
25 use vmm_sys_util::eventfd::EventFd;
26 
27 #[derive(Debug, Error)]
28 pub enum Error {
29     /// Cannot clone File.
30     #[error("Error cloning File: {0}")]
31     FileClone(#[source] io::Error),
32 
33     /// Cannot create epoll context.
34     #[error("Error creating epoll context: {0}")]
35     Epoll(#[source] io::Error),
36 
37     /// Cannot handle the VM input stream.
38     #[error("Error handling VM input: {0:?}")]
39     ReadInput(#[source] io::Error),
40 
41     /// Cannot queue input to the serial device.
42     #[error("Error queuing input to the serial device: {0}")]
43     QueueInput(#[source] vmm_sys_util::errno::Error),
44 
45     /// Cannot flush output on the serial buffer.
46     #[error("Error flushing serial device's output buffer: {0}")]
47     FlushOutput(#[source] io::Error),
48 
49     /// Cannot make the file descriptor non-blocking.
50     #[error("Error making input file descriptor non-blocking: {0}")]
51     SetNonBlocking(#[source] io::Error),
52 
53     /// Cannot create EventFd.
54     #[error("Error creating EventFd: {0}")]
55     EventFd(#[source] io::Error),
56 
57     /// Cannot spawn SerialManager thread.
58     #[error("Error spawning SerialManager thread: {0}")]
59     SpawnSerialManager(#[source] io::Error),
60 
61     /// Cannot bind to Unix socket
62     #[error("Error binding to socket: {0}")]
63     BindUnixSocket(#[source] io::Error),
64 
65     /// Cannot accept connection from Unix socket
66     #[error("Error accepting connection: {0}")]
67     AcceptConnection(#[source] io::Error),
68 
69     /// Cannot clone the UnixStream
70     #[error("Error cloning UnixStream: {0}")]
71     CloneUnixStream(#[source] io::Error),
72 
73     /// Cannot shutdown the connection
74     #[error("Error shutting down a connection: {0}")]
75     ShutdownConnection(#[source] io::Error),
76 
77     /// Cannot remove the serial socket
78     #[error("Error removing serial socket: {0}")]
79     RemoveUnixSocket(#[source] io::Error),
80 }
81 pub type Result<T> = result::Result<T, Error>;
82 
83 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
84 #[repr(u64)]
85 pub enum EpollDispatch {
86     File = 0,
87     Kill = 1,
88     Socket = 2,
89     Unknown,
90 }
91 
92 impl From<u64> for EpollDispatch {
93     fn from(v: u64) -> Self {
94         use EpollDispatch::*;
95         match v {
96             0 => File,
97             1 => Kill,
98             2 => Socket,
99             _ => Unknown,
100         }
101     }
102 }
103 
104 pub struct SerialManager {
105     #[cfg(target_arch = "x86_64")]
106     serial: Arc<Mutex<Serial>>,
107     #[cfg(target_arch = "aarch64")]
108     serial: Arc<Mutex<Pl011>>,
109     epoll_file: File,
110     in_file: File,
111     kill_evt: EventFd,
112     handle: Option<thread::JoinHandle<()>>,
113     pty_write_out: Option<Arc<AtomicBool>>,
114     mode: ConsoleOutputMode,
115     socket_path: Option<PathBuf>,
116 }
117 
118 impl SerialManager {
119     pub fn new(
120         #[cfg(target_arch = "x86_64")] serial: Arc<Mutex<Serial>>,
121         #[cfg(target_arch = "aarch64")] serial: Arc<Mutex<Pl011>>,
122         pty_pair: Option<Arc<Mutex<PtyPair>>>,
123         mode: ConsoleOutputMode,
124         socket: Option<PathBuf>,
125     ) -> Result<Option<Self>> {
126         let mut socket_path: Option<PathBuf> = None;
127 
128         let in_file = match mode {
129             ConsoleOutputMode::Pty => {
130                 if let Some(pty_pair) = pty_pair {
131                     pty_pair
132                         .lock()
133                         .unwrap()
134                         .main
135                         .try_clone()
136                         .map_err(Error::FileClone)?
137                 } else {
138                     return Ok(None);
139                 }
140             }
141             ConsoleOutputMode::Tty => {
142                 // If running on an interactive TTY then accept input
143                 // SAFETY: trivially safe
144                 if unsafe { libc::isatty(libc::STDIN_FILENO) == 1 } {
145                     // SAFETY: STDIN_FILENO is a valid fd
146                     let stdin_clone = unsafe { File::from_raw_fd(libc::dup(libc::STDIN_FILENO)) };
147                     // SAFETY: FFI calls with correct arguments
148                     let ret = unsafe {
149                         let mut flags = libc::fcntl(stdin_clone.as_raw_fd(), libc::F_GETFL);
150                         flags |= libc::O_NONBLOCK;
151                         libc::fcntl(stdin_clone.as_raw_fd(), libc::F_SETFL, flags)
152                     };
153 
154                     if ret < 0 {
155                         return Err(Error::SetNonBlocking(std::io::Error::last_os_error()));
156                     }
157 
158                     stdin_clone
159                 } else {
160                     return Ok(None);
161                 }
162             }
163             ConsoleOutputMode::Socket => {
164                 if let Some(path_in_socket) = socket {
165                     socket_path = Some(path_in_socket.clone());
166                     let listener = UnixListener::bind(path_in_socket.as_path())
167                         .map_err(Error::BindUnixSocket)?;
168                     // SAFETY: listener is valid and will return valid fd
169                     unsafe { File::from_raw_fd(listener.into_raw_fd()) }
170                 } else {
171                     return Ok(None);
172                 }
173             }
174             _ => return Ok(None),
175         };
176 
177         let epoll_fd = epoll::create(true).map_err(Error::Epoll)?;
178         let kill_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::EventFd)?;
179 
180         epoll::ctl(
181             epoll_fd,
182             epoll::ControlOptions::EPOLL_CTL_ADD,
183             kill_evt.as_raw_fd(),
184             epoll::Event::new(epoll::Events::EPOLLIN, EpollDispatch::Kill as u64),
185         )
186         .map_err(Error::Epoll)?;
187 
188         let epoll_fd_data = if mode == ConsoleOutputMode::Socket {
189             EpollDispatch::Socket
190         } else {
191             EpollDispatch::File
192         };
193 
194         epoll::ctl(
195             epoll_fd,
196             epoll::ControlOptions::EPOLL_CTL_ADD,
197             in_file.as_raw_fd(),
198             epoll::Event::new(epoll::Events::EPOLLIN, epoll_fd_data as u64),
199         )
200         .map_err(Error::Epoll)?;
201 
202         let mut pty_write_out = None;
203         if mode == ConsoleOutputMode::Pty {
204             let write_out = Arc::new(AtomicBool::new(false));
205             pty_write_out = Some(write_out.clone());
206             let writer = in_file.try_clone().map_err(Error::FileClone)?;
207             let buffer = SerialBuffer::new(Box::new(writer), write_out);
208             serial
209                 .as_ref()
210                 .lock()
211                 .unwrap()
212                 .set_out(Some(Box::new(buffer)));
213         }
214 
215         // Use 'File' to enforce closing on 'epoll_fd'
216         // SAFETY: epoll_fd is valid
217         let epoll_file = unsafe { File::from_raw_fd(epoll_fd) };
218 
219         Ok(Some(SerialManager {
220             serial,
221             epoll_file,
222             in_file,
223             kill_evt,
224             handle: None,
225             pty_write_out,
226             mode,
227             socket_path,
228         }))
229     }
230 
231     // This function should be called when the other end of the PTY is
232     // connected. It verifies if this is the first time it's been invoked
233     // after the connection happened, and if that's the case it flushes
234     // all output from the serial to the PTY. Otherwise, it's a no-op.
235     fn trigger_pty_flush(
236         #[cfg(target_arch = "x86_64")] serial: &Arc<Mutex<Serial>>,
237         #[cfg(target_arch = "aarch64")] serial: &Arc<Mutex<Pl011>>,
238         pty_write_out: Option<&Arc<AtomicBool>>,
239     ) -> Result<()> {
240         if let Some(pty_write_out) = &pty_write_out {
241             if pty_write_out.load(Ordering::Acquire) {
242                 return Ok(());
243             }
244 
245             pty_write_out.store(true, Ordering::Release);
246 
247             serial
248                 .lock()
249                 .unwrap()
250                 .flush_output()
251                 .map_err(Error::FlushOutput)?;
252         }
253 
254         Ok(())
255     }
256 
257     pub fn start_thread(&mut self, exit_evt: EventFd) -> Result<()> {
258         // Don't allow this to be run if the handle exists
259         if self.handle.is_some() {
260             warn!("Tried to start multiple SerialManager threads, ignoring");
261             return Ok(());
262         }
263 
264         let epoll_fd = self.epoll_file.as_raw_fd();
265         let mut in_file = self.in_file.try_clone().map_err(Error::FileClone)?;
266         let serial = self.serial.clone();
267         let pty_write_out = self.pty_write_out.clone();
268         //SAFETY: in_file is has a valid fd
269         let listener = unsafe { UnixListener::from_raw_fd(self.in_file.as_raw_fd()) };
270         let mut reader: Option<UnixStream> = None;
271         let mode = self.mode.clone();
272 
273         // In case of PTY, we want to be able to detect a connection on the
274         // other end of the PTY. This is done by detecting there's no event
275         // triggered on the epoll, which is the reason why we want the
276         // epoll_wait() function to return after the timeout expired.
277         // In case of TTY, we don't expect to detect such behavior, which is
278         // why we can afford to block until an actual event is triggered.
279         let timeout = if pty_write_out.is_some() { 500 } else { -1 };
280 
281         let thread = thread::Builder::new()
282             .name("serial-manager".to_string())
283             .spawn(move || {
284                 std::panic::catch_unwind(AssertUnwindSafe(move || {
285                     // 3 for File, Kill, and Unknown
286                     const EPOLL_EVENTS_LEN: usize = 3;
287 
288                     let mut events =
289                         [epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN];
290 
291                     loop {
292                         let num_events = match epoll::wait(epoll_fd, timeout, &mut events[..]) {
293                             Ok(res) => res,
294                             Err(e) => {
295                                 if e.kind() == io::ErrorKind::Interrupted {
296                                     // It's well defined from the epoll_wait() syscall
297                                     // documentation that the epoll loop can be interrupted
298                                     // before any of the requested events occurred or the
299                                     // timeout expired. In both those cases, epoll_wait()
300                                     // returns an error of type EINTR, but this should not
301                                     // be considered as a regular error. Instead it is more
302                                     // appropriate to retry, by calling into epoll_wait().
303                                     continue;
304                                 } else {
305                                     return Err(Error::Epoll(e));
306                                 }
307                             }
308                         };
309 
310                         if mode != ConsoleOutputMode::Socket && num_events == 0 {
311                             // This very specific case happens when the serial is connected
312                             // to a PTY. We know EPOLLHUP is always present when there's nothing
313                             // connected at the other end of the PTY. That's why getting no event
314                             // means we can flush the output of the serial through the PTY.
315                             Self::trigger_pty_flush(&serial, pty_write_out.as_ref())?;
316                             continue;
317                         }
318 
319                         for event in events.iter().take(num_events) {
320                             let dispatch_event: EpollDispatch = event.data.into();
321                             match dispatch_event {
322                                 EpollDispatch::Unknown => {
323                                     let event = event.data;
324                                     warn!("Unknown serial manager loop event: {}", event);
325                                 }
326                                 EpollDispatch::Socket => {
327                                     // New connection request arrived.
328                                     // Shutdown the previous connection, if any
329                                     if let Some(previous_reader) = reader {
330                                         previous_reader
331                                             .shutdown(Shutdown::Both)
332                                             .map_err(Error::AcceptConnection)?;
333                                     }
334                                     // Events on the listening socket will be connection requests.
335                                     // Accept them, create a reader and a writer.
336                                     let (unix_stream, _) =
337                                         listener.accept().map_err(Error::AcceptConnection)?;
338                                     let writer =
339                                         unix_stream.try_clone().map_err(Error::CloneUnixStream)?;
340                                     reader = Some(
341                                         unix_stream.try_clone().map_err(Error::CloneUnixStream)?,
342                                     );
343 
344                                     epoll::ctl(
345                                         epoll_fd,
346                                         epoll::ControlOptions::EPOLL_CTL_ADD,
347                                         unix_stream.into_raw_fd(),
348                                         epoll::Event::new(
349                                             epoll::Events::EPOLLIN,
350                                             EpollDispatch::File as u64,
351                                         ),
352                                     )
353                                     .map_err(Error::Epoll)?;
354                                     serial.lock().unwrap().set_out(Some(Box::new(writer)));
355                                 }
356                                 EpollDispatch::File => {
357                                     if event.events & libc::EPOLLIN as u32 != 0 {
358                                         let mut input = [0u8; 64];
359                                         let count = match mode {
360                                             ConsoleOutputMode::Socket => {
361                                                 if let Some(mut serial_reader) = reader.as_ref() {
362                                                     let count = serial_reader
363                                                         .read(&mut input)
364                                                         .map_err(Error::ReadInput)?;
365                                                     if count == 0 {
366                                                         info!("Remote end closed serial socket");
367                                                         serial_reader
368                                                             .shutdown(Shutdown::Both)
369                                                             .map_err(Error::ShutdownConnection)?;
370                                                         reader = None;
371                                                         serial
372                                                             .as_ref()
373                                                             .lock()
374                                                             .unwrap()
375                                                             .set_out(None);
376                                                     }
377                                                     count
378                                                 } else {
379                                                     0
380                                                 }
381                                             }
382                                             _ => in_file
383                                                 .read(&mut input)
384                                                 .map_err(Error::ReadInput)?,
385                                         };
386 
387                                         // Replace "\n" with "\r" to deal with Windows SAC (#1170)
388                                         if count == 1 && input[0] == 0x0a {
389                                             input[0] = 0x0d;
390                                         }
391 
392                                         serial
393                                             .as_ref()
394                                             .lock()
395                                             .unwrap()
396                                             .queue_input_bytes(&input[..count])
397                                             .map_err(Error::QueueInput)?;
398                                     }
399                                     if event.events & libc::EPOLLHUP as u32 != 0 {
400                                         if let Some(pty_write_out) = &pty_write_out {
401                                             pty_write_out.store(false, Ordering::Release);
402                                         }
403                                         // It's really important to sleep here as this will prevent
404                                         // the current thread from consuming 100% of the CPU cycles
405                                         // when waiting for someone to connect to the PTY.
406                                         std::thread::sleep(std::time::Duration::from_millis(500));
407                                     } else {
408                                         // If the EPOLLHUP flag is not up on the associated event, we
409                                         // can assume the other end of the PTY is connected and therefore
410                                         // we can flush the output of the serial to it.
411                                         Self::trigger_pty_flush(&serial, pty_write_out.as_ref())?;
412                                     }
413                                 }
414                                 EpollDispatch::Kill => {
415                                     info!("KILL_EVENT received, stopping epoll loop");
416                                     return Ok(());
417                                 }
418                             }
419                         }
420                     }
421                 }))
422                 .map_err(|_| {
423                     error!("serial-manager thread panicked");
424                     exit_evt.write(1).ok()
425                 })
426                 .ok();
427             })
428             .map_err(Error::SpawnSerialManager)?;
429         self.handle = Some(thread);
430         Ok(())
431     }
432 }
433 
434 impl Drop for SerialManager {
435     fn drop(&mut self) {
436         self.kill_evt.write(1).ok();
437         if let Some(handle) = self.handle.take() {
438             handle.join().ok();
439         }
440         if self.mode == ConsoleOutputMode::Socket {
441             if let Some(socket_path) = self.socket_path.as_ref() {
442                 std::fs::remove_file(socket_path.as_os_str())
443                     .map_err(Error::RemoveUnixSocket)
444                     .ok();
445             }
446         }
447     }
448 }
449