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