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