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