diff --git a/src/main.rs b/src/main.rs index eca2d2fa4..2e70913f8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,7 +21,6 @@ use thiserror::Error; use vmm::config; use vmm_sys_util::eventfd::EventFd; use vmm_sys_util::signal::block_signal; -use vmm_sys_util::terminal::Terminal; #[cfg(feature = "dhat-heap")] #[global_allocator] @@ -586,14 +585,6 @@ fn main() { } }; - // SAFETY: trivially safe - let on_tty = unsafe { libc::isatty(libc::STDIN_FILENO) } != 0; - if on_tty { - // Don't forget to set the terminal in canonical mode - // before to exit. - std::io::stdin().lock().set_canon_mode().unwrap(); - } - #[cfg(feature = "dhat-heap")] drop(_profiler); diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 76ca37759..d6e2adc48 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -70,6 +70,7 @@ use std::os::unix::fs::OpenOptionsExt; use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; use std::path::PathBuf; use std::result; +use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; use std::time::Instant; use tracer::trace_scoped; @@ -100,6 +101,7 @@ use vm_migration::{ use vm_virtio::AccessPlatform; use vm_virtio::VirtioDeviceType; use vmm_sys_util::eventfd::EventFd; +use vmm_sys_util::terminal::Terminal; #[cfg(target_arch = "aarch64")] const MMIO_LEN: u64 = 0x1000; @@ -823,6 +825,9 @@ pub struct DeviceManager { // pty foreground status, console_resize_pipe: Option>, + // Are any devices using the tty? + on_tty: Option>, + // Interrupt controller #[cfg(target_arch = "x86_64")] interrupt_controller: Option>>, @@ -1107,6 +1112,7 @@ impl DeviceManager { serial_manager: None, console_pty: None, console_resize_pipe: None, + on_tty: None, virtio_mem_devices: Vec::new(), #[cfg(target_arch = "aarch64")] gpio_device: None, @@ -1154,6 +1160,7 @@ impl DeviceManager { serial_pty: Option, console_pty: Option, console_resize_pipe: Option, + on_tty: Arc, ) -> DeviceManagerResult<()> { trace_scoped!("create_devices"); @@ -1215,7 +1222,9 @@ impl DeviceManager { serial_pty, console_pty, console_resize_pipe, + &on_tty, )?; + self.on_tty = Some(on_tty); if let Some(tpm) = self.config.clone().lock().unwrap().tpm.as_ref() { let tpm_dev = self.add_tpm_device(tpm.socket.clone())?; @@ -1856,7 +1865,7 @@ impl DeviceManager { Ok(()) } - fn set_raw_mode(&self, f: &mut File) -> vmm_sys_util::errno::Result<()> { + fn set_raw_mode(&self, f: &mut dyn AsRawFd) -> vmm_sys_util::errno::Result<()> { // SAFETY: FFI call. Variable t is guaranteed to be a valid termios from modify_mode. self.modify_mode(f.as_raw_fd(), |t| unsafe { cfmakeraw(t) }) } @@ -1886,6 +1895,7 @@ impl DeviceManager { virtio_devices: &mut Vec, console_pty: Option, resize_pipe: Option, + on_tty: &Arc, ) -> DeviceManagerResult>> { let console_config = self.config.lock().unwrap().console.clone(); let endpoint = match console_config.mode { @@ -1925,7 +1935,12 @@ impl DeviceManager { return vmm_sys_util::errno::errno_result().map_err(DeviceManagerError::DupFd); } // SAFETY: stdout is valid and owned solely by us. - let stdout = unsafe { File::from_raw_fd(stdout) }; + let mut stdout = unsafe { File::from_raw_fd(stdout) }; + + on_tty.store(true, Ordering::SeqCst); + + // Make sure stdout is in raw mode, if it's a terminal. + let _ = self.set_raw_mode(&mut stdout); // If an interactive TTY then we can accept input // SAFETY: FFI call. Trivially safe. @@ -1997,6 +2012,7 @@ impl DeviceManager { serial_pty: Option, console_pty: Option, console_resize_pipe: Option, + on_tty: &Arc, ) -> DeviceManagerResult> { let serial_config = self.config.lock().unwrap().serial.clone(); let serial_writer: Option> = match serial_config.mode { @@ -2018,7 +2034,12 @@ impl DeviceManager { } None } - ConsoleOutputMode::Tty => Some(Box::new(stdout())), + ConsoleOutputMode::Tty => { + let mut out = stdout(); + on_tty.store(true, Ordering::SeqCst); + let _ = self.set_raw_mode(&mut out); + Some(Box::new(out)) + } ConsoleOutputMode::Off | ConsoleOutputMode::Null => None, }; if serial_config.mode != ConsoleOutputMode::Off { @@ -2045,8 +2066,12 @@ impl DeviceManager { }; } - let console_resizer = - self.add_virtio_console_device(virtio_devices, console_pty, console_resize_pipe)?; + let console_resizer = self.add_virtio_console_device( + virtio_devices, + console_pty, + console_resize_pipe, + on_tty, + )?; Ok(Arc::new(Console { console_resizer })) } @@ -4625,5 +4650,11 @@ impl Drop for DeviceManager { for handle in self.virtio_devices.drain(..) { handle.virtio_device.lock().unwrap().shutdown(); } + + if let Some(ref on_tty) = self.on_tty { + if on_tty.load(Ordering::SeqCst) { + let _ = std::io::stdin().lock().set_canon_mode(); + } + } } } diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index a25cab35d..146f4e56c 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -41,6 +41,7 @@ use std::os::unix::net::UnixListener; use std::os::unix::net::UnixStream; use std::panic::AssertUnwindSafe; use std::path::PathBuf; +use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::mpsc::{Receiver, RecvError, SendError, Sender}; use std::sync::{Arc, Mutex}; use std::time::Instant; @@ -407,12 +408,13 @@ pub struct Vmm { activate_evt: EventFd, signals: Option, threads: Vec>, + on_tty: Arc, } impl Vmm { pub const HANDLED_SIGNALS: [i32; 2] = [SIGTERM, SIGINT]; - fn signal_handler(mut signals: Signals, on_tty: bool, exit_evt: &EventFd) { + fn signal_handler(mut signals: Signals, on_tty: Arc, exit_evt: &EventFd) { for sig in &Self::HANDLED_SIGNALS { unblock_signal(*sig).unwrap(); } @@ -422,7 +424,7 @@ impl Vmm { SIGTERM | SIGINT => { if exit_evt.write(1).is_err() { // Resetting the terminal is usually done as the VMM exits - if on_tty { + if on_tty.load(Ordering::SeqCst) { io::stdin() .lock() .set_canon_mode() @@ -442,8 +444,7 @@ impl Vmm { Ok(signals) => { self.signals = Some(signals.handle()); let exit_evt = self.exit_evt.try_clone().map_err(Error::EventFdClone)?; - // SAFETY: trivially safe - let on_tty = unsafe { libc::isatty(libc::STDIN_FILENO) } != 0; + let on_tty = Arc::clone(&self.on_tty); let signal_handler_seccomp_filter = get_seccomp_filter( &self.seccomp_action, @@ -532,6 +533,7 @@ impl Vmm { activate_evt, signals: None, threads: vec![], + on_tty: Arc::new(AtomicBool::new(false)), }) } @@ -582,6 +584,7 @@ impl Vmm { None, None, None, + Arc::clone(&self.on_tty), None, None, None, @@ -680,6 +683,7 @@ impl Vmm { None, None, None, + Arc::clone(&self.on_tty), Some(snapshot), Some(source_url), Some(restore_cfg.prefault), @@ -760,6 +764,7 @@ impl Vmm { serial_pty, console_pty, console_resize_pipe, + Arc::clone(&self.on_tty), None, None, None, @@ -1262,6 +1267,7 @@ impl Vmm { None, None, None, + Arc::clone(&self.on_tty), Some(snapshot), ) .map_err(|e| { diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index f450e0cc7..16d6afe48 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -81,6 +81,7 @@ use std::num::Wrapping; use std::ops::Deref; use std::os::unix::net::UnixStream; use std::panic::AssertUnwindSafe; +use std::sync::atomic::AtomicBool; use std::sync::{Arc, Mutex, RwLock}; use std::time::Instant; use std::{result, str, thread}; @@ -98,7 +99,6 @@ use vm_migration::{ use vmm_sys_util::eventfd::EventFd; use vmm_sys_util::signal::unblock_signal; use vmm_sys_util::sock_ctrl_msg::ScmSocket; -use vmm_sys_util::terminal::Terminal; /// Errors associated with VM management #[derive(Debug, Error)] @@ -141,9 +141,6 @@ pub enum Error { #[error("Error from device manager: {0:?}")] DeviceManager(DeviceManagerError), - #[error("Cannot setup terminal in raw mode: {0}")] - SetTerminalRaw(#[source] vmm_sys_util::errno::Error), - #[error("Cannot spawn a signal handler thread: {0}")] SignalHandlerSpawn(#[source] io::Error), @@ -437,7 +434,6 @@ pub struct Vm { threads: Vec>, device_manager: Arc>, config: Arc>, - on_tty: bool, signals: Option, state: RwLock, cpu_manager: Arc>, @@ -473,6 +469,7 @@ impl Vm { serial_pty: Option, console_pty: Option, console_resize_pipe: Option, + on_tty: Arc, snapshot: Option, ) -> Result { trace_scoped!("Vm::new_from_memory_manager"); @@ -594,12 +591,9 @@ impl Vm { device_manager .lock() .unwrap() - .create_devices(serial_pty, console_pty, console_resize_pipe) + .create_devices(serial_pty, console_pty, console_resize_pipe, on_tty) .map_err(Error::DeviceManager)?; - // SAFETY: trivially safe - let on_tty = unsafe { libc::isatty(libc::STDIN_FILENO) } != 0; - #[cfg(feature = "tdx")] let kernel = config .lock() @@ -641,7 +635,6 @@ impl Vm { initramfs, device_manager, config, - on_tty, threads: Vec::with_capacity(1), signals: None, state: RwLock::new(vm_state), @@ -751,6 +744,7 @@ impl Vm { serial_pty: Option, console_pty: Option, console_resize_pipe: Option, + on_tty: Arc, snapshot: Option, source_url: Option<&str>, prefault: Option, @@ -820,6 +814,7 @@ impl Vm { serial_pty, console_pty, console_resize_pipe, + on_tty, snapshot, ) } @@ -1971,17 +1966,6 @@ impl Vm { Ok(()) } - fn setup_tty(&self) -> Result<()> { - if self.on_tty { - io::stdin() - .lock() - .set_raw_mode() - .map_err(Error::SetTerminalRaw)?; - } - - Ok(()) - } - // Creates ACPI tables // In case of TDX being used, this is a no-op since the tables will be // created and passed when populating the HOB. @@ -2036,7 +2020,6 @@ impl Vm { let rsdp_addr = self.create_acpi_tables(); self.setup_signal_handler()?; - self.setup_tty()?; // Load kernel synchronously or if asynchronous then wait for load to // finish. @@ -2151,7 +2134,6 @@ impl Vm { .map_err(Error::CpuManager)?; self.setup_signal_handler()?; - self.setup_tty()?; event!("vm", "restored"); Ok(())