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