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