diff --git a/virtio-devices/src/console.rs b/virtio-devices/src/console.rs index ad6fb0de0..81a3425ce 100644 --- a/virtio-devices/src/console.rs +++ b/virtio-devices/src/console.rs @@ -14,9 +14,9 @@ use libc::EFD_NONBLOCK; use seccompiler::{apply_filter, SeccompAction}; use std::cmp; use std::collections::VecDeque; +use std::fs::File; use std::io; -use std::io::Write; -use std::ops::DerefMut; +use std::io::{Read, Write}; use std::os::unix::io::AsRawFd; use std::result; use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; @@ -40,6 +40,8 @@ const OUTPUT_QUEUE_EVENT: u16 = EPOLL_HELPER_EVENT_LAST + 2; const INPUT_EVENT: u16 = EPOLL_HELPER_EVENT_LAST + 3; // Console configuration change event is triggered. const CONFIG_EVENT: u16 = EPOLL_HELPER_EVENT_LAST + 4; +// File written to (input ready) +const FILE_EVENT: u16 = EPOLL_HELPER_EVENT_LAST + 5; //Console size feature bit const VIRTIO_CONSOLE_F_SIZE: u64 = 0; @@ -61,7 +63,7 @@ struct ConsoleEpollHandler { mem: GuestMemoryAtomic, interrupt_cb: Arc, in_buffer: Arc>>, - out: Arc>>, + endpoint: Endpoint, input_queue_evt: EventFd, output_queue_evt: EventFd, input_evt: EventFd, @@ -70,6 +72,42 @@ struct ConsoleEpollHandler { pause_evt: EventFd, } +pub enum Endpoint { + File(File), + FilePair(File, File), + Null, +} + +impl Endpoint { + fn out_file(&self) -> Option<&File> { + match self { + Self::File(f) => Some(f), + Self::FilePair(f, _) => Some(f), + Self::Null => None, + } + } + + fn in_file(&self) -> Option<&File> { + match self { + Self::File(_) => None, + Self::FilePair(_, f) => Some(f), + Self::Null => None, + } + } +} + +impl Clone for Endpoint { + fn clone(&self) -> Self { + match self { + Self::File(f) => Self::File(f.try_clone().unwrap()), + Self::FilePair(f_out, f_in) => { + Self::FilePair(f_out.try_clone().unwrap(), f_in.try_clone().unwrap()) + } + Self::Null => Self::Null, + } + } +} + impl ConsoleEpollHandler { /* * Each port of virtio console device has one receive @@ -127,14 +165,10 @@ impl ConsoleEpollHandler { let mem = self.mem.memory(); for avail_desc in trans_queue.iter(&mem) { let len; - let mut out = self.out.lock().unwrap(); - let _ = mem.write_to( - avail_desc.addr, - &mut out.deref_mut(), - avail_desc.len as usize, - ); - let _ = out.flush(); - + if let Some(ref mut out) = self.endpoint.out_file() { + let _ = mem.write_to(avail_desc.addr, out, avail_desc.len as usize); + let _ = out.flush(); + } len = avail_desc.len; used_desc_heads[used_count] = (avail_desc.index, len); used_count += 1; @@ -165,6 +199,9 @@ impl ConsoleEpollHandler { helper.add_event(self.output_queue_evt.as_raw_fd(), OUTPUT_QUEUE_EVENT)?; helper.add_event(self.input_evt.as_raw_fd(), INPUT_EVENT)?; helper.add_event(self.config_evt.as_raw_fd(), CONFIG_EVENT)?; + if let Some(in_file) = self.endpoint.in_file() { + helper.add_event(in_file.as_raw_fd(), FILE_EVENT)?; + } helper.run(paused, paused_sync, self)?; Ok(()) @@ -217,6 +254,22 @@ impl EpollHelperHandler for ConsoleEpollHandler { return true; } } + FILE_EVENT => { + let mut input = [0u8; 64]; + if let Some(ref mut in_file) = self.endpoint.in_file() { + if let Ok(count) = in_file.read(&mut input) { + let mut in_buffer = self.in_buffer.lock().unwrap(); + in_buffer.extend(&input[..count]); + } + + if self.process_input_queue() { + if let Err(e) = self.signal_used_queue() { + error!("Failed to signal used queue: {:?}", e); + return true; + } + } + } + } _ => { error!("Unknown event for virtio-console"); return true; @@ -226,22 +279,14 @@ impl EpollHelperHandler for ConsoleEpollHandler { } } -/// Input device. -pub struct ConsoleInput { - input_evt: EventFd, +/// Resize handler +pub struct ConsoleResizer { config_evt: EventFd, - in_buffer: Arc>>, config: Arc>, acked_features: AtomicU64, } -impl ConsoleInput { - pub fn queue_input_bytes(&self, input: &[u8]) { - let mut in_buffer = self.in_buffer.lock().unwrap(); - in_buffer.extend(input); - let _ = self.input_evt.write(1); - } - +impl ConsoleResizer { pub fn update_console_size(&self, cols: u16, rows: u16) { if self .acked_features @@ -276,9 +321,10 @@ pub struct Console { common: VirtioCommon, id: String, config: Arc>, - input: Arc, - out: Arc>>, + resizer: Arc, + endpoint: Endpoint, seccomp_action: SeccompAction, + in_buffer: Arc>>, } #[derive(Versionize)] @@ -295,25 +341,22 @@ impl Console { /// Create a new virtio console device that gets random data from /dev/urandom. pub fn new( id: String, - out: Box, + endpoint: Endpoint, cols: u16, rows: u16, iommu: bool, seccomp_action: SeccompAction, - ) -> io::Result<(Console, Arc)> { + ) -> io::Result<(Console, Arc)> { let mut avail_features = 1u64 << VIRTIO_F_VERSION_1 | 1u64 << VIRTIO_CONSOLE_F_SIZE; if iommu { avail_features |= 1u64 << VIRTIO_F_IOMMU_PLATFORM; } - let input_evt = EventFd::new(EFD_NONBLOCK).unwrap(); let config_evt = EventFd::new(EFD_NONBLOCK).unwrap(); let console_config = Arc::new(Mutex::new(VirtioConsoleConfig::new(cols, rows))); - let console_input = Arc::new(ConsoleInput { - input_evt, + let resizer = Arc::new(ConsoleResizer { config_evt, - in_buffer: Arc::new(Mutex::new(VecDeque::new())), config: console_config.clone(), acked_features: AtomicU64::new(0), }); @@ -330,11 +373,12 @@ impl Console { }, id, config: console_config, - input: console_input.clone(), - out: Arc::new(Mutex::new(out)), + resizer: resizer.clone(), + endpoint, seccomp_action, + in_buffer: Arc::new(Mutex::new(VecDeque::new())), }, - console_input, + resizer, )) } @@ -343,7 +387,7 @@ impl Console { avail_features: self.common.avail_features, acked_features: self.common.acked_features, config: *(self.config.lock().unwrap()), - in_buffer: self.input.in_buffer.lock().unwrap().clone().into(), + in_buffer: self.in_buffer.lock().unwrap().clone().into(), } } @@ -351,7 +395,7 @@ impl Console { self.common.avail_features = state.avail_features; self.common.acked_features = state.acked_features; *(self.config.lock().unwrap()) = state.config; - *(self.input.in_buffer.lock().unwrap()) = state.in_buffer.clone().into(); + *(self.in_buffer.lock().unwrap()) = state.in_buffer.clone().into(); } } @@ -393,7 +437,7 @@ impl VirtioDevice for Console { mut queue_evts: Vec, ) -> ActivateResult { self.common.activate(&queues, &queue_evts, &interrupt_cb)?; - self.input + self.resizer .acked_features .store(self.common.acked_features, Ordering::Relaxed); @@ -404,17 +448,18 @@ impl VirtioDevice for Console { } let (kill_evt, pause_evt) = self.common.dup_eventfds(); + let input_evt = EventFd::new(EFD_NONBLOCK).unwrap(); let mut handler = ConsoleEpollHandler { queues, mem, interrupt_cb, - in_buffer: self.input.in_buffer.clone(), - out: self.out.clone(), + in_buffer: self.in_buffer.clone(), + endpoint: self.endpoint.clone(), input_queue_evt: queue_evts.remove(0), output_queue_evt: queue_evts.remove(0), - input_evt: self.input.input_evt.try_clone().unwrap(), - config_evt: self.input.config_evt.try_clone().unwrap(), + input_evt, + config_evt: self.resizer.config_evt.try_clone().unwrap(), kill_evt, pause_evt, }; diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 4ee580de6..26dc1edca 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -73,7 +73,7 @@ use seccompiler::SeccompAction; use std::collections::HashMap; use std::convert::TryInto; use std::fs::{read_link, File, OpenOptions}; -use std::io::{self, sink, stdout, Seek, SeekFrom}; +use std::io::{self, stdout, Seek, SeekFrom}; use std::mem::zeroed; use std::num::Wrapping; use std::os::unix::fs::OpenOptionsExt; @@ -88,7 +88,7 @@ use vfio_ioctls::{VfioContainer, VfioDevice}; use virtio_devices::transport::VirtioPciDevice; use virtio_devices::transport::VirtioTransport; use virtio_devices::vhost_user::VhostUserConfig; -use virtio_devices::{DmaRemapping, IommuMapping}; +use virtio_devices::{DmaRemapping, Endpoint, IommuMapping}; use virtio_devices::{VirtioSharedMemory, VirtioSharedMemoryList}; use vm_allocator::SystemAllocator; #[cfg(feature = "kvm")] @@ -535,7 +535,7 @@ pub struct Console { serial: Option>>, #[cfg(target_arch = "aarch64")] serial: Option>>, - virtio_console_input: Option>, + console_resizer: Option>, input: Option, } @@ -552,21 +552,9 @@ impl Console { Ok(()) } - pub fn queue_input_bytes_console(&self, out: &[u8]) { - if self.virtio_console_input.is_some() { - self.virtio_console_input - .as_ref() - .unwrap() - .queue_input_bytes(out); - } - } - pub fn update_console_size(&self, cols: u16, rows: u16) { - if self.virtio_console_input.is_some() { - self.virtio_console_input - .as_ref() - .unwrap() - .update_console_size(cols, rows) + if let Some(resizer) = self.console_resizer.as_ref() { + resizer.update_console_size(cols, rows) } } @@ -1695,6 +1683,81 @@ impl DeviceManager { self.modify_mode(f.as_raw_fd(), |t| t.c_lflag &= !(ICANON | ECHO | ISIG)) } + fn add_virtio_console_device( + &mut self, + virtio_devices: &mut Vec<(VirtioDeviceArc, bool, String)>, + console_pty: Option, + ) -> DeviceManagerResult>> { + let console_config = self.config.lock().unwrap().console.clone(); + let endpoint = match console_config.mode { + ConsoleOutputMode::File => { + let file = File::create(console_config.file.as_ref().unwrap()) + .map_err(DeviceManagerError::ConsoleOutputFileOpen)?; + Endpoint::File(file) + } + ConsoleOutputMode::Pty => { + if let Some(pty) = console_pty { + self.config.lock().unwrap().console.file = Some(pty.path.clone()); + let file = pty.main.try_clone().unwrap(); + self.console_pty = Some(Arc::new(Mutex::new(pty))); + Endpoint::FilePair(file.try_clone().unwrap(), file) + } else { + let (main, mut sub, path) = + create_pty(false).map_err(DeviceManagerError::ConsolePtyOpen)?; + self.set_raw_mode(&mut sub) + .map_err(DeviceManagerError::SetPtyRaw)?; + self.config.lock().unwrap().console.file = Some(path.clone()); + let file = main.try_clone().unwrap(); + self.console_pty = Some(Arc::new(Mutex::new(PtyPair { main, sub, path }))); + Endpoint::FilePair(file.try_clone().unwrap(), file) + } + } + ConsoleOutputMode::Tty => { + // If an interactive TTY then we can accept input + if unsafe { libc::isatty(libc::STDIN_FILENO) == 1 } { + Endpoint::FilePair( + // Duplicating the file descriptors like this is needed as otherwise + // they will be closed on a reboot and the numbers reused + unsafe { File::from_raw_fd(libc::dup(libc::STDOUT_FILENO)) }, + unsafe { File::from_raw_fd(libc::dup(libc::STDIN_FILENO)) }, + ) + } else { + Endpoint::File(unsafe { File::from_raw_fd(libc::dup(libc::STDOUT_FILENO)) }) + } + } + ConsoleOutputMode::Null => Endpoint::Null, + ConsoleOutputMode::Off => return Ok(None), + }; + let (col, row) = get_win_size(); + let id = String::from(CONSOLE_DEVICE_NAME); + + let (virtio_console_device, console_resizer) = virtio_devices::Console::new( + id.clone(), + endpoint, + col, + row, + self.force_iommu | console_config.iommu, + self.seccomp_action.clone(), + ) + .map_err(DeviceManagerError::CreateVirtioConsole)?; + let virtio_console_device = Arc::new(Mutex::new(virtio_console_device)); + virtio_devices.push(( + Arc::clone(&virtio_console_device) as VirtioDeviceArc, + console_config.iommu, + id.clone(), + )); + + // Fill the device tree with a new node. In case of restore, we + // know there is nothing to do, so we can simply override the + // existing entry. + self.device_tree + .lock() + .unwrap() + .insert(id.clone(), device_node!(id, virtio_console_device)); + + Ok(Some(console_resizer)) + } + fn add_console_device( &mut self, interrupt_manager: &Arc>, @@ -1702,6 +1765,7 @@ impl DeviceManager { serial_pty: Option, console_pty: Option, ) -> DeviceManagerResult> { + let console_config = self.config.lock().unwrap().console.clone(); let serial_config = self.config.lock().unwrap().serial.clone(); let serial_writer: Option> = match serial_config.mode { ConsoleOutputMode::File => Some(Box::new( @@ -1736,66 +1800,7 @@ impl DeviceManager { None }; - // Create serial and virtio-console - let console_config = self.config.lock().unwrap().console.clone(); - let console_writer: Option> = match console_config.mode { - ConsoleOutputMode::File => Some(Box::new( - File::create(console_config.file.as_ref().unwrap()) - .map_err(DeviceManagerError::ConsoleOutputFileOpen)?, - )), - ConsoleOutputMode::Pty => { - if let Some(pty) = console_pty { - self.config.lock().unwrap().console.file = Some(pty.path.clone()); - let writer = pty.main.try_clone().unwrap(); - self.console_pty = Some(Arc::new(Mutex::new(pty))); - Some(Box::new(writer)) - } else { - let (main, mut sub, path) = - create_pty(false).map_err(DeviceManagerError::ConsolePtyOpen)?; - self.set_raw_mode(&mut sub) - .map_err(DeviceManagerError::SetPtyRaw)?; - self.config.lock().unwrap().console.file = Some(path.clone()); - let writer = main.try_clone().unwrap(); - self.console_pty = Some(Arc::new(Mutex::new(PtyPair { main, sub, path }))); - Some(Box::new(writer)) - } - } - ConsoleOutputMode::Tty => Some(Box::new(stdout())), - ConsoleOutputMode::Null => Some(Box::new(sink())), - ConsoleOutputMode::Off => None, - }; - let (col, row) = get_win_size(); - let virtio_console_input = if let Some(writer) = console_writer { - let id = String::from(CONSOLE_DEVICE_NAME); - - let (virtio_console_device, virtio_console_input) = virtio_devices::Console::new( - id.clone(), - writer, - col, - row, - self.force_iommu | console_config.iommu, - self.seccomp_action.clone(), - ) - .map_err(DeviceManagerError::CreateVirtioConsole)?; - let virtio_console_device = Arc::new(Mutex::new(virtio_console_device)); - virtio_devices.push(( - Arc::clone(&virtio_console_device) as VirtioDeviceArc, - console_config.iommu, - id.clone(), - )); - - // Fill the device tree with a new node. In case of restore, we - // know there is nothing to do, so we can simply override the - // existing entry. - self.device_tree - .lock() - .unwrap() - .insert(id.clone(), device_node!(id, virtio_console_device)); - - Some(virtio_console_input) - } else { - None - }; + let console_resizer = self.add_virtio_console_device(virtio_devices, console_pty)?; let input = if serial_config.mode.input_enabled() { Some(ConsoleInput::Serial) @@ -1807,8 +1812,8 @@ impl DeviceManager { Ok(Arc::new(Console { serial, - virtio_console_input, input, + console_resizer, })) } diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index aecc399b2..a06d8ac9e 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -150,8 +150,7 @@ pub enum EpollDispatch { Stdin = 2, Api = 3, ActivateVirtioDevices = 4, - ConsolePty = 5, - SerialPty = 6, + SerialPty = 5, Unknown, } @@ -164,8 +163,7 @@ impl From for EpollDispatch { 2 => Stdin, 3 => Api, 4 => ActivateVirtioDevices, - 5 => ConsolePty, - 6 => SerialPty, + 5 => SerialPty, _ => Unknown, } } @@ -324,10 +322,6 @@ impl Vmm { let reset_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::EventFdCreate)?; let activate_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::EventFdCreate)?; - if unsafe { libc::isatty(libc::STDIN_FILENO as i32) } != 0 { - epoll.add_stdin().map_err(Error::Epoll)?; - } - epoll .add_event(&exit_evt, EpollDispatch::Exit) .map_err(Error::Epoll)?; @@ -400,11 +394,14 @@ impl Vmm { .add_event(&serial_pty.main, EpollDispatch::SerialPty) .map_err(VmError::EventfdError)?; }; - if let Some(console_pty) = vm.console_pty() { - self.epoll - .add_event(&console_pty.main, EpollDispatch::ConsolePty) - .map_err(VmError::EventfdError)?; - }; + if matches!( + vm_config.lock().unwrap().serial.mode, + config::ConsoleOutputMode::Tty + ) && unsafe { libc::isatty(libc::STDIN_FILENO as i32) } != 0 + { + self.epoll.add_stdin().map_err(VmError::EventfdError)?; + } + self.vm = Some(vm); } } @@ -1302,7 +1299,7 @@ impl Vmm { .map_err(Error::ActivateVirtioDevices)?; } } - event @ (EpollDispatch::ConsolePty | EpollDispatch::SerialPty) => { + event @ EpollDispatch::SerialPty => { if let Some(ref vm) = self.vm { vm.handle_pty(event).map_err(Error::Pty)?; } diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index b6c1216a3..ed3557e3b 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -1928,15 +1928,6 @@ impl Vm { .map_err(Error::Console)?; } }; - } else if matches!(event, EpollDispatch::ConsolePty) { - if let Some(mut pty) = dm.console_pty() { - let mut out = [0u8; 64]; - let count = pty.main.read(&mut out).map_err(Error::PtyConsole)?; - let console = dm.console(); - if console.input_enabled() { - console.queue_input_bytes_console(&out[..count]) - } - }; } Ok(()) @@ -1964,15 +1955,6 @@ impl Vm { .console() .queue_input_bytes_serial(&out[..count]) .map_err(Error::Console)?; - } else if matches!( - self.config.lock().unwrap().console.mode, - ConsoleOutputMode::Tty - ) { - self.device_manager - .lock() - .unwrap() - .console() - .queue_input_bytes_console(&out[..count]) } Ok(())