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 if unsafe { libc::isatty(libc::STDIN_FILENO) == 1 } { 114 let stdin_clone = unsafe { File::from_raw_fd(libc::dup(libc::STDIN_FILENO)) }; 115 let ret = unsafe { 116 let mut flags = libc::fcntl(stdin_clone.as_raw_fd(), libc::F_GETFL); 117 flags |= libc::O_NONBLOCK; 118 libc::fcntl(stdin_clone.as_raw_fd(), libc::F_SETFL, flags) 119 }; 120 121 if ret < 0 { 122 return Err(Error::SetNonBlocking(std::io::Error::last_os_error())); 123 } 124 125 stdin_clone 126 } else { 127 return Ok(None); 128 } 129 } 130 _ => return Ok(None), 131 }; 132 133 let epoll_fd = epoll::create(true).map_err(Error::Epoll)?; 134 let kill_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::EventFd)?; 135 136 epoll::ctl( 137 epoll_fd, 138 epoll::ControlOptions::EPOLL_CTL_ADD, 139 kill_evt.as_raw_fd(), 140 epoll::Event::new(epoll::Events::EPOLLIN, EpollDispatch::Kill as u64), 141 ) 142 .map_err(Error::Epoll)?; 143 144 epoll::ctl( 145 epoll_fd, 146 epoll::ControlOptions::EPOLL_CTL_ADD, 147 in_file.as_raw_fd(), 148 epoll::Event::new(epoll::Events::EPOLLIN, EpollDispatch::File as u64), 149 ) 150 .map_err(Error::Epoll)?; 151 152 let mut pty_write_out = None; 153 if mode == ConsoleOutputMode::Pty { 154 let write_out = Arc::new(AtomicBool::new(false)); 155 pty_write_out = Some(write_out.clone()); 156 let writer = in_file.try_clone().map_err(Error::FileClone)?; 157 let buffer = SerialBuffer::new(Box::new(writer), write_out); 158 serial.as_ref().lock().unwrap().set_out(Box::new(buffer)); 159 } 160 161 // Use 'File' to enforce closing on 'epoll_fd' 162 let epoll_file = unsafe { File::from_raw_fd(epoll_fd) }; 163 164 Ok(Some(SerialManager { 165 serial, 166 epoll_file, 167 in_file, 168 kill_evt, 169 handle: None, 170 pty_write_out, 171 })) 172 } 173 174 // This function should be called when the other end of the PTY is 175 // connected. It verifies if this is the first time it's been invoked 176 // after the connection happened, and if that's the case it flushes 177 // all output from the serial to the PTY. Otherwise, it's a no-op. 178 fn trigger_pty_flush( 179 #[cfg(target_arch = "x86_64")] serial: &Arc<Mutex<Serial>>, 180 #[cfg(target_arch = "aarch64")] serial: &Arc<Mutex<Pl011>>, 181 pty_write_out: Option<&Arc<AtomicBool>>, 182 ) -> Result<()> { 183 if let Some(pty_write_out) = &pty_write_out { 184 if pty_write_out.load(Ordering::Acquire) { 185 return Ok(()); 186 } 187 188 pty_write_out.store(true, Ordering::Release); 189 190 serial 191 .lock() 192 .unwrap() 193 .flush_output() 194 .map_err(Error::FlushOutput)?; 195 } 196 197 Ok(()) 198 } 199 200 pub fn start_thread(&mut self, exit_evt: EventFd) -> Result<()> { 201 // Don't allow this to be run if the handle exists 202 if self.handle.is_some() { 203 warn!("Tried to start multiple SerialManager threads, ignoring"); 204 return Ok(()); 205 } 206 207 let epoll_fd = self.epoll_file.as_raw_fd(); 208 let mut in_file = self.in_file.try_clone().map_err(Error::FileClone)?; 209 let serial = self.serial.clone(); 210 let pty_write_out = self.pty_write_out.clone(); 211 212 // In case of PTY, we want to be able to detect a connection on the 213 // other end of the PTY. This is done by detecting there's no event 214 // triggered on the epoll, which is the reason why we want the 215 // epoll_wait() function to return after the timeout expired. 216 // In case of TTY, we don't expect to detect such behavior, which is 217 // why we can afford to block until an actual event is triggered. 218 let timeout = if pty_write_out.is_some() { 500 } else { -1 }; 219 220 let thread = thread::Builder::new() 221 .name("serial-manager".to_string()) 222 .spawn(move || { 223 std::panic::catch_unwind(AssertUnwindSafe(move || { 224 // 3 for File, Kill, and Unknown 225 const EPOLL_EVENTS_LEN: usize = 3; 226 227 let mut events = 228 vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN]; 229 230 loop { 231 let num_events = match epoll::wait(epoll_fd, timeout, &mut events[..]) { 232 Ok(res) => res, 233 Err(e) => { 234 if e.kind() == io::ErrorKind::Interrupted { 235 // It's well defined from the epoll_wait() syscall 236 // documentation that the epoll loop can be interrupted 237 // before any of the requested events occurred or the 238 // timeout expired. In both those cases, epoll_wait() 239 // returns an error of type EINTR, but this should not 240 // be considered as a regular error. Instead it is more 241 // appropriate to retry, by calling into epoll_wait(). 242 continue; 243 } else { 244 return Err(Error::Epoll(e)); 245 } 246 } 247 }; 248 249 if num_events == 0 { 250 // This very specific case happens when the serial is connected 251 // to a PTY. We know EPOLLHUP is always present when there's nothing 252 // connected at the other end of the PTY. That's why getting no event 253 // means we can flush the output of the serial through the PTY. 254 Self::trigger_pty_flush(&serial, pty_write_out.as_ref())?; 255 continue; 256 } 257 258 for event in events.iter().take(num_events) { 259 let dispatch_event: EpollDispatch = event.data.into(); 260 match dispatch_event { 261 EpollDispatch::Unknown => { 262 let event = event.data; 263 warn!("Unknown serial manager loop event: {}", event); 264 } 265 EpollDispatch::File => { 266 if event.events & libc::EPOLLIN as u32 != 0 { 267 let mut input = [0u8; 64]; 268 let count = 269 in_file.read(&mut input).map_err(Error::ReadInput)?; 270 271 // Replace "\n" with "\r" to deal with Windows SAC (#1170) 272 if count == 1 && input[0] == 0x0a { 273 input[0] = 0x0d; 274 } 275 276 serial 277 .as_ref() 278 .lock() 279 .unwrap() 280 .queue_input_bytes(&input[..count]) 281 .map_err(Error::QueueInput)?; 282 } 283 if event.events & libc::EPOLLHUP as u32 != 0 { 284 if let Some(pty_write_out) = &pty_write_out { 285 pty_write_out.store(false, Ordering::Release); 286 } 287 // It's really important to sleep here as this will prevent 288 // the current thread from consuming 100% of the CPU cycles 289 // when waiting for someone to connect to the PTY. 290 std::thread::sleep(std::time::Duration::from_millis(500)); 291 } else { 292 // If the EPOLLHUP flag is not up on the associated event, we 293 // can assume the other end of the PTY is connected and therefore 294 // we can flush the output of the serial to it. 295 Self::trigger_pty_flush(&serial, pty_write_out.as_ref())?; 296 } 297 } 298 EpollDispatch::Kill => { 299 info!("KILL event received, stopping epoll loop"); 300 return Ok(()); 301 } 302 } 303 } 304 } 305 })) 306 .map_err(|_| { 307 error!("serial-manager thread panicked"); 308 exit_evt.write(1).ok() 309 }) 310 .ok(); 311 }) 312 .map_err(Error::SpawnSerialManager)?; 313 self.handle = Some(thread); 314 Ok(()) 315 } 316 } 317 318 impl Drop for SerialManager { 319 fn drop(&mut self) { 320 self.kill_evt.write(1).ok(); 321 if let Some(handle) = self.handle.take() { 322 handle.join().ok(); 323 } 324 } 325 } 326