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