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