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