From 93dd6af9174f4838c042c3eac86b8a6272f263e7 Mon Sep 17 00:00:00 2001 From: Timos Ampelikiotis Date: Thu, 10 Oct 2024 11:42:55 +0300 Subject: [PATCH 1/3] vhost-device-console: use worker's epoll for input events Eliminate the use of select and 'nix' package. This is done by registering the input events (stdin or tcplistener) onto the main worker's epoll. Signed-off-by: Timos Ampelikiotis --- staging/vhost-device-console/Cargo.toml | 1 - staging/vhost-device-console/src/backend.rs | 39 +- staging/vhost-device-console/src/console.rs | 2 - .../vhost-device-console/src/vhu_console.rs | 456 ++++++++++-------- 4 files changed, 264 insertions(+), 234 deletions(-) diff --git a/staging/vhost-device-console/Cargo.toml b/staging/vhost-device-console/Cargo.toml index b4c4f0225..068dd02e5 100644 --- a/staging/vhost-device-console/Cargo.toml +++ b/staging/vhost-device-console/Cargo.toml @@ -17,7 +17,6 @@ xen = ["vm-memory/xen", "vhost/xen", "vhost-user-backend/xen"] [dependencies] console = "0.15.7" crossterm = "0.28.1" -nix = "0.26.4" queues = "1.0.2" clap = { version = "4.5", features = ["derive"] } env_logger = "0.11" diff --git a/staging/vhost-device-console/src/backend.rs b/staging/vhost-device-console/src/backend.rs index 62d6745cd..61f03a194 100644 --- a/staging/vhost-device-console/src/backend.rs +++ b/staging/vhost-device-console/src/backend.rs @@ -5,7 +5,7 @@ // // SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause -use log::{error, info, warn}; +use log::{error, info}; use std::any::Any; use std::collections::HashMap; use std::path::PathBuf; @@ -28,6 +28,8 @@ pub(crate) enum Error { SocketCountInvalid(usize), #[error("Could not create console backend: {0}")] CouldNotCreateBackend(crate::vhu_console::Error), + #[error("Could not create console backend: {0}")] + CouldNotInitBackend(crate::vhu_console::Error), #[error("Could not create daemon: {0}")] CouldNotCreateDaemon(vhost_user_backend::Error), #[error("Fatal error: {0}")] @@ -92,6 +94,12 @@ pub(crate) fn start_backend_server( VhostUserConsoleBackend::new(arc_controller).map_err(Error::CouldNotCreateBackend)?, )); + vu_console_backend + .write() + .unwrap() + .assign_input_method(tcp_addr.clone()) + .map_err(Error::CouldNotInitBackend)?; + let mut daemon = VhostUserDaemon::new( String::from("vhost-device-console-backend"), vu_console_backend.clone(), @@ -102,26 +110,15 @@ pub(crate) fn start_backend_server( let vring_workers = daemon.get_epoll_handlers(); vu_console_backend .read() - .unwrap() - .set_vring_worker(&vring_workers[0]); - - // Start the corresponding console thread - let read_handle = if backend == BackendType::Nested { - VhostUserConsoleBackend::start_console_thread(&vu_console_backend) - } else { - VhostUserConsoleBackend::start_tcp_console_thread(&vu_console_backend, tcp_addr.clone()) - }; - - daemon.serve(&socket).map_err(Error::ServeFailed)?; - - // Kill console input thread - vu_console_backend.read().unwrap().kill_console_thread(); - - // Wait for read thread to exit - match read_handle.join() { - Ok(_) => info!("The read thread returned successfully"), - Err(e) => warn!("The read thread returned the error: {:?}", e), - } + .expect("Cannot open as write\n") + .set_vring_worker(vring_workers[0].clone()); + + daemon.serve(&socket).map_err(|e| { + // Even if daemon stops unexpectedly, the backend should + // be terminated properly (disable raw mode). + vu_console_backend.read().unwrap().prepare_exit(); + Error::ServeFailed(e) + })?; } } diff --git a/staging/vhost-device-console/src/console.rs b/staging/vhost-device-console/src/console.rs index 079fe0cb0..b02b5d04c 100644 --- a/staging/vhost-device-console/src/console.rs +++ b/staging/vhost-device-console/src/console.rs @@ -20,7 +20,6 @@ pub enum BackendType { pub(crate) struct ConsoleController { config: VirtioConsoleConfig, pub backend: BackendType, - pub exit: bool, } impl ConsoleController { @@ -33,7 +32,6 @@ impl ConsoleController { emerg_wr: 64.into(), }, backend, - exit: false, } } diff --git a/staging/vhost-device-console/src/vhu_console.rs b/staging/vhost-device-console/src/vhu_console.rs index d5cca74a0..f33e2d19d 100644 --- a/staging/vhost-device-console/src/vhu_console.rs +++ b/staging/vhost-device-console/src/vhu_console.rs @@ -11,22 +11,20 @@ use crate::virtio_console::{ VIRTIO_CONSOLE_F_MULTIPORT, VIRTIO_CONSOLE_PORT_ADD, VIRTIO_CONSOLE_PORT_NAME, VIRTIO_CONSOLE_PORT_OPEN, VIRTIO_CONSOLE_PORT_READY, }; -use console::Key; use crossterm::terminal::{disable_raw_mode, enable_raw_mode}; -use log::{error, trace}; -use nix::sys::select::{select, FdSet}; -use std::os::fd::AsRawFd; +use log::{error, trace, warn}; +use queues::{IsQueue, Queue}; +use std::net::TcpListener; +use std::os::fd::{AsRawFd, RawFd}; use std::slice::from_raw_parts; use std::sync::{Arc, RwLock}; -use std::thread::JoinHandle; use std::{ convert, - io::{self, Result as IoResult}, + io::{self, Read, Result as IoResult, Write}, }; use thiserror::Error as ThisError; use vhost::vhost_user::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures}; -use vhost_user_backend::VringEpollHandler; -use vhost_user_backend::{VhostUserBackendMut, VringRwLock, VringT}; +use vhost_user_backend::{VhostUserBackendMut, VringEpollHandler, VringRwLock, VringT}; use virtio_bindings::bindings::virtio_config::{VIRTIO_F_NOTIFY_ON_EMPTY, VIRTIO_F_VERSION_1}; use virtio_bindings::bindings::virtio_ring::{ VIRTIO_RING_F_EVENT_IDX, VIRTIO_RING_F_INDIRECT_DESC, @@ -38,13 +36,6 @@ use vm_memory::{ use vmm_sys_util::epoll::EventSet; use vmm_sys_util::eventfd::{EventFd, EFD_NONBLOCK}; -use console::Term; -use queues::{IsQueue, Queue}; -use std::io::Read; -use std::io::Write; -use std::net::TcpListener; -use std::thread::spawn; - /// Virtio configuration const QUEUE_SIZE: usize = 128; const NUM_QUEUES: usize = 4; @@ -63,6 +54,9 @@ const CTRL_TX_QUEUE: u16 = 3; /// needs to write to the RX control queue. const BACKEND_RX_EFD: u16 = (NUM_QUEUES + 1) as u16; const BACKEND_CTRL_RX_EFD: u16 = (NUM_QUEUES + 2) as u16; +const KEY_EFD: u16 = (NUM_QUEUES + 3) as u16; +const LISTENER_EFD: u16 = (NUM_QUEUES + 4) as u16; +const EXIT_EFD: u16 = (NUM_QUEUES + 5) as u16; /// Port name - Need to be updated when MULTIPORT feature /// is supported for more than one devices. @@ -88,6 +82,12 @@ pub(crate) enum Error { EventFdFailed, #[error("Failed to add control message in the internal queue")] RxCtrlQueueAddFailed, + #[error("Error adding epoll")] + EpollAdd, + #[error("Error removing epoll")] + EpollRemove, + #[error("Error creating epoll")] + EpollFdCreate, } impl convert::From for io::Error { @@ -96,6 +96,10 @@ impl convert::From for io::Error { } } +// Define a new trait that combines Read and Write +pub trait ReadWrite: Read + Write {} +impl ReadWrite for T {} + // SAFETY: The layout of the structure is fixed and can be initialized by // reading its content from byte array. unsafe impl ByteValued for VirtioConsoleControl {} @@ -105,10 +109,15 @@ pub(crate) struct VhostUserConsoleBackend { acked_features: u64, event_idx: bool, rx_ctrl_fifo: Queue, - rx_data_fifo: Queue, + rx_data_fifo: Queue, + epoll_fd: i32, + stream_fd: Option, pub(crate) ready: bool, pub(crate) ready_to_write: bool, pub(crate) output_queue: Queue, + pub(crate) stdin: Option>, + pub(crate) listener: Option, + pub(crate) stream: Option>, pub(crate) rx_event: EventFd, pub(crate) rx_ctrl_event: EventFd, pub(crate) exit_event: EventFd, @@ -120,14 +129,19 @@ type ConsoleDescriptorChain = DescriptorChain>) -> Result { Ok(VhostUserConsoleBackend { - controller, + controller: controller.clone(), event_idx: false, rx_ctrl_fifo: Queue::new(), rx_data_fifo: Queue::new(), + epoll_fd: epoll::create(false).map_err(|_| Error::EpollFdCreate)?, + stream_fd: None, acked_features: 0x0, ready: false, ready_to_write: false, output_queue: Queue::new(), + stdin: None, + stream: None, + listener: None, rx_event: EventFd::new(EFD_NONBLOCK).map_err(|_| Error::EventFdFailed)?, rx_ctrl_event: EventFd::new(EFD_NONBLOCK).map_err(|_| Error::EventFdFailed)?, exit_event: EventFd::new(EFD_NONBLOCK).map_err(|_| Error::EventFdFailed)?, @@ -135,6 +149,24 @@ impl VhostUserConsoleBackend { }) } + pub fn assign_input_method(&mut self, tcpaddr_str: String) -> Result<()> { + if self.controller.read().unwrap().backend == BackendType::Nested { + // Enable raw mode for local terminal if backend is nested + enable_raw_mode().expect("Raw mode error"); + + let stdin_fd = io::stdin().as_raw_fd(); + let stdin: Box = Box::new(io::stdin()); + self.stdin = Some(stdin); + + Self::epoll_register(self.epoll_fd.as_raw_fd(), stdin_fd, epoll::Events::EPOLLIN) + .map_err(|_| Error::EpollAdd)?; + } else { + let listener = TcpListener::bind(tcpaddr_str.clone()).expect("asdasd"); + self.listener = Some(listener); + } + Ok(()) + } + fn print_console_frame(&self, control_msg: VirtioConsoleControl) { trace!("id 0x{:x}", control_msg.id.to_native()); trace!("event 0x{:x}", control_msg.event.to_native()); @@ -157,22 +189,26 @@ impl VhostUserConsoleBackend { .writer(&atomic_mem) .map_err(|_| Error::DescriptorWriteFailed)?; - let response: String = match self.rx_data_fifo.remove() { - Ok(item) => item, - _ => { - return Ok(false); - } - }; + let avail_data_len = writer.available_bytes(); + let queue_len = self.rx_data_fifo.size(); + let min_limit = std::cmp::min(queue_len, avail_data_len); + + for _i in 0..min_limit { + let response: u8 = match self.rx_data_fifo.remove() { + Ok(item) => item, + _ => { + return Ok(true); + } + }; - for b in response.bytes() { writer - .write_obj::(b) + .write_obj::(response) .map_err(|_| Error::DescriptorWriteFailed)?; - } - vring - .add_used(desc_chain.head_index(), writer.bytes_written() as u32) - .map_err(|_| Error::AddUsedElemFailed(RX_QUEUE))?; + vring + .add_used(desc_chain.head_index(), writer.bytes_written() as u32) + .map_err(|_| Error::AddUsedElemFailed(RX_QUEUE))?; + } } Ok(true) @@ -210,8 +246,8 @@ impl VhostUserConsoleBackend { } else { self.output_queue .add(my_string) - .expect("Failed to add element in the output queue"); - //.map_err(|_| Error::RxCtrlQueueAddFailed)?; + .map_err(|_| Error::RxCtrlQueueAddFailed)?; + self.write_tcp_stream(); } vring @@ -438,7 +474,7 @@ impl VhostUserConsoleBackend { /// Set self's VringWorker. pub(crate) fn set_vring_worker( &self, - vring_worker: &Arc>>>, + vring_worker: Arc>>>, ) { let rx_event_fd = self.rx_event.as_raw_fd(); vring_worker @@ -453,187 +489,170 @@ impl VhostUserConsoleBackend { u64::from(BACKEND_CTRL_RX_EFD), ) .unwrap(); + + let exit_event_fd = self.exit_event.as_raw_fd(); + vring_worker + .register_listener(exit_event_fd, EventSet::IN, u64::from(EXIT_EFD)) + .unwrap(); + + let epoll_fd = self.epoll_fd.as_raw_fd(); + vring_worker + .register_listener(epoll_fd, EventSet::IN, u64::from(KEY_EFD)) + .unwrap(); + + if self.controller.read().unwrap().backend == BackendType::Network { + let listener_fd = self.listener.as_ref().expect("asd").as_raw_fd(); + vring_worker + .register_listener(listener_fd, EventSet::IN, u64::from(LISTENER_EFD)) + .unwrap(); + } } - pub(crate) fn start_tcp_console_thread( - vhu_console: &Arc>, - tcplisener_str: String, - ) -> JoinHandle> { - let vhu_console = Arc::clone(vhu_console); - spawn(move || { - loop { - let ready = vhu_console.read().unwrap().ready_to_write; - let exit = vhu_console.read().unwrap().controller.read().unwrap().exit; + /// Register a file with an epoll to listen for events in evset. + pub fn epoll_register(epoll_fd: RawFd, fd: RawFd, evset: epoll::Events) -> Result<()> { + epoll::ctl( + epoll_fd, + epoll::ControlOptions::EPOLL_CTL_ADD, + fd, + epoll::Event::new(evset, fd as u64), + ) + .map_err(|_| Error::EpollAdd)?; + Ok(()) + } - if exit { - trace!("Thread exits!"); - break; - } else if ready { - let listener = match TcpListener::bind(tcplisener_str.clone()) { - Ok(listener) => listener, - Err(e) => { - eprintln!("Failed to bind to {}: {}", tcplisener_str, e); - return Ok(()); - } - }; - listener.set_nonblocking(true).expect("Non-blocking error"); - - println!("Server listening on address: {}", tcplisener_str.clone()); - for stream in listener.incoming() { - match stream { - Ok(mut stream) => { - trace!("New connection"); - stream.set_nonblocking(true).expect("Non-blocking error"); - - let mut buffer = [0; 1024]; - loop { - let exit = - vhu_console.read().unwrap().controller.read().unwrap().exit; - if exit { - trace!("Thread exits!"); - return Ok(()); - } - // Write to the stream - if vhu_console.read().unwrap().output_queue.size() > 0 { - let byte_stream = vhu_console - .write() - .unwrap() - .output_queue - .remove() - .expect("Error removing element from output queue") - .into_bytes(); - if let Err(e) = stream.write_all(&byte_stream) { - eprintln!("Error writing to stream: {}", e); - } - } - match stream.read(&mut buffer) { - Ok(bytes_read) => { - if bytes_read == 0 { - println!("Close connection"); - break; - } - trace!( - "Received: {}", - String::from_utf8_lossy(&buffer[..bytes_read]) - ); - let input_buffer = - String::from_utf8_lossy(&buffer[..bytes_read]) - .to_string(); - vhu_console - .write() - .unwrap() - .rx_data_fifo - .add(input_buffer) - .unwrap(); - vhu_console.write().unwrap().rx_event.write(1).unwrap(); - } - Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { - continue; - } - Err(ref e) - if e.kind() == io::ErrorKind::BrokenPipe - || e.kind() == io::ErrorKind::ConnectionReset => - { - trace!("Stream has been closed."); - break; - } - Err(e) => { - eprintln!("Error reading from socket: {}", e); - } - } - } - } - Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { - let exit = - vhu_console.read().unwrap().controller.read().unwrap().exit; - if exit { - trace!("Thread exits!"); - return Ok(()); - } - continue; - } - Err(e) => { - eprintln!("Error accepting connection: {}", e); - break; - } - } + /// Remove a file from the epoll. + pub fn epoll_unregister(epoll_fd: RawFd, fd: RawFd) -> Result<()> { + epoll::ctl( + epoll_fd, + epoll::ControlOptions::EPOLL_CTL_DEL, + fd, + epoll::Event::new(epoll::Events::empty(), 0), + ) + .map_err(|_| Error::EpollRemove)?; + + Ok(()) + } + + fn create_new_stream_thread(&mut self) { + // Accept only one incoming connection + if let Some(stream) = self.listener.as_ref().expect("asd").incoming().next() { + match stream { + Ok(stream) => { + let local_addr = self + .listener + .as_ref() + .expect("No listener") + .local_addr() + .unwrap(); + println!("New connection on: {}", local_addr); + let stream_raw_fd = stream.as_raw_fd(); + self.stream_fd = Some(stream_raw_fd); + if let Err(err) = Self::epoll_register( + self.epoll_fd.as_raw_fd(), + stream_raw_fd, + epoll::Events::EPOLLIN, + ) { + warn!("Failed to register with epoll: {:?}", err); } + + let stream: Box = Box::new(stream); + self.stream = Some(stream); + self.write_tcp_stream(); + } + Err(e) => { + eprintln!("Stream error: {}", e); } } - Ok(()) - }) + } } - /// Start console thread. - pub(crate) fn start_console_thread( - vhu_console: &Arc>, - ) -> JoinHandle> { - let vhu_console = Arc::clone(vhu_console); - - let exit_eventfd = vhu_console.read().unwrap().exit_event.as_raw_fd(); - // Spawn a new thread to handle input. - spawn(move || { - let term = Term::stdout(); - let mut fdset = FdSet::new(); - fdset.insert(term.as_raw_fd()); - fdset.insert(exit_eventfd); - let max_fd = fdset.highest().expect("Failed to read fdset!") + 1; + fn write_tcp_stream(&mut self) { + if self.stream.is_some() { + while self.output_queue.size() > 0 { + let byte_stream = self + .output_queue + .remove() + .expect("Error removing element from output queue") + .into_bytes(); + + if let Err(e) = self + .stream + .as_mut() + .expect("Stream not found") + .write_all(&byte_stream) + { + eprintln!("Error writing to stream: {}", e); + } + } + } + } - loop { - let ready = vhu_console.read().unwrap().ready_to_write; - let exit = vhu_console.read().unwrap().controller.read().unwrap().exit; + fn read_tcp_stream(&mut self) { + let mut buffer = [0; 1024]; + match self.stream.as_mut().expect("No stream").read(&mut buffer) { + Ok(bytes_read) => { + if bytes_read == 0 { + let local_addr = self + .listener + .as_ref() + .expect("No listener") + .local_addr() + .unwrap(); + println!("Close connection on: {}", local_addr); + if let Err(err) = Self::epoll_unregister( + self.epoll_fd.as_raw_fd(), + self.stream_fd.expect("No stream fd"), + ) { + warn!("Failed to register with epoll: {:?}", err); + } + return; + } + if self.ready_to_write { + for byte in buffer.iter().take(bytes_read) { + self.rx_data_fifo.add(*byte).unwrap(); + } + self.rx_event.write(1).unwrap(); + } + } + Err(e) => { + eprintln!("Error reading from socket: {}", e); + } + } + } - if exit { - trace!("Exit!"); - break; - } else if ready { - let mut fdset_clone = fdset; - enable_raw_mode().expect("Raw mode error"); - - match select(Some(max_fd), Some(&mut fdset_clone), None, None, None) { - Ok(_num_fds) => { - let exit = vhu_console.read().unwrap().controller.read().unwrap().exit; - if (fdset_clone.contains(exit_eventfd)) && exit { - trace!("Exit!"); - break; - } - - if fdset_clone.contains(term.as_raw_fd()) { - if let Some(character) = match term.read_key().unwrap() { - Key::Char(character) => Some(character), - Key::Enter => Some('\n'), - Key::Tab => Some('\t'), - Key::Backspace => Some('\u{8}'), - _ => None, - } { - // Pass the data to vhu_console and trigger an EventFd - let input_buffer = character.to_string(); - vhu_console - .write() - .unwrap() - .rx_data_fifo - .add(input_buffer) - .unwrap(); - vhu_console.write().unwrap().rx_event.write(1).unwrap(); - } - } - } - Err(e) => { - eprintln!("Error in select: {}", e); - break; - } + fn read_char_thread(&mut self) -> IoResult<()> { + let mut bytes = [0; 1]; + match self.stdin.as_mut().expect("No stdin").read(&mut bytes) { + Ok(read_len) => { + if read_len > 0 { + // If the user presses ^C then exit + if bytes[0] == 3 { + disable_raw_mode().expect("Raw mode error"); + trace!("Termination!\n"); + std::process::exit(0); + } + + // If backend is ready pass the data to vhu_console + // and trigger an EventFd. + if self.ready_to_write { + self.rx_data_fifo.add(bytes[0]).unwrap(); + self.rx_event.write(1).unwrap(); } } + Ok(()) + } + Err(e) => { + eprintln!("Read stdin error: {}", e); + Err(e) } + } + } + pub fn prepare_exit(&self) { + /* For the nested backend */ + if self.controller.read().unwrap().backend == BackendType::Nested { disable_raw_mode().expect("Raw mode error"); - Ok(()) - }) - } - pub fn kill_console_thread(&self) { - trace!("Kill thread"); - self.controller.write().unwrap().exit = true; - self.exit_event.write(1).unwrap(); + } } } @@ -703,19 +722,24 @@ impl VhostUserBackendMut for VhostUserConsoleBackend { vrings: &[VringRwLock], _thread_id: usize, ) -> IoResult<()> { - if device_event == RX_QUEUE { - // Check if there are any available data - if self.rx_data_fifo.size() == 0 { - return Ok(()); - } - }; + if device_event == EXIT_EFD { + self.prepare_exit(); + return Ok(()); + } + + if device_event == LISTENER_EFD { + self.create_new_stream_thread(); + return Ok(()); + } - if device_event == CTRL_RX_QUEUE { - // Check if there are any available data and the device is ready - if (!self.ready) || (self.rx_ctrl_fifo.size() == 0) { + if device_event == KEY_EFD { + if self.controller.read().unwrap().backend == BackendType::Nested { + return self.read_char_thread(); + } else { + self.read_tcp_stream(); return Ok(()); } - }; + } let vring = if device_event == BACKEND_RX_EFD { &vrings[RX_QUEUE as usize] @@ -729,12 +753,24 @@ impl VhostUserBackendMut for VhostUserConsoleBackend { loop { vring.disable_notification().unwrap(); match device_event { - RX_QUEUE => self.process_rx_queue(vring), + RX_QUEUE => { + if self.rx_data_fifo.size() != 0 { + self.process_rx_queue(vring) + } else { + break; + } + } TX_QUEUE => { self.ready_to_write = true; self.process_tx_queue(vring) } - CTRL_RX_QUEUE => self.process_ctrl_rx_queue(vring), + CTRL_RX_QUEUE => { + if self.ready && (self.rx_ctrl_fifo.size() != 0) { + self.process_ctrl_rx_queue(vring) + } else { + break; + } + } CTRL_TX_QUEUE => self.process_ctrl_tx_queue(vring), BACKEND_RX_EFD => { let _ = self.rx_event.read(); From 0bf48e84301f25204b13f2333d615bc971b70ac1 Mon Sep 17 00:00:00 2001 From: Timos Ampelikiotis Date: Thu, 10 Oct 2024 11:46:52 +0300 Subject: [PATCH 2/3] vhost-device-console: improve tests Add tests for the new input events and increase overall test coverage. Signed-off-by: Timos Ampelikiotis --- staging/coverage_config_x86_64.json | 2 +- staging/vhost-device-console/src/backend.rs | 58 ++------ .../vhost-device-console/src/vhu_console.rs | 138 ++++++++++++------ 3 files changed, 109 insertions(+), 89 deletions(-) diff --git a/staging/coverage_config_x86_64.json b/staging/coverage_config_x86_64.json index c1371fdb9..0bc27369b 100644 --- a/staging/coverage_config_x86_64.json +++ b/staging/coverage_config_x86_64.json @@ -1,5 +1,5 @@ { - "coverage_score": 80.31, + "coverage_score": 86.38, "exclude_path": "", "crate_features": "" } diff --git a/staging/vhost-device-console/src/backend.rs b/staging/vhost-device-console/src/backend.rs index 61f03a194..5f2198050 100644 --- a/staging/vhost-device-console/src/backend.rs +++ b/staging/vhost-device-console/src/backend.rs @@ -80,7 +80,7 @@ impl VuConsoleConfig { } } -// This is the public API through which an external program starts the +/// This is the public API through which an external program starts the /// vhost-device-console backend server. pub(crate) fn start_backend_server( socket: PathBuf, @@ -240,71 +240,43 @@ mod tests { assert!(VuConsoleConfig::try_from(args).is_ok()); } - fn test_backend_start_and_stop(args: ConsoleArgs) { + fn test_backend_start_and_stop(args: ConsoleArgs) -> Result<()> { let config = VuConsoleConfig::try_from(args).expect("Wrong config"); let tcp_addrs = config.generate_tcp_addrs(); let backend = config.backend; - for (_socket, tcp_addr) in config + for (socket, tcp_addr) in config .generate_socket_paths() .into_iter() .zip(tcp_addrs.iter()) { - let controller = ConsoleController::new(backend); - let arc_controller = Arc::new(RwLock::new(controller)); - let vu_console_backend = Arc::new(RwLock::new( - VhostUserConsoleBackend::new(arc_controller) - .map_err(Error::CouldNotCreateBackend) - .expect("Fail create vhuconsole backend"), - )); - - let mut _daemon = VhostUserDaemon::new( - String::from("vhost-device-console-backend"), - vu_console_backend.clone(), - GuestMemoryAtomic::new(GuestMemoryMmap::new()), - ) - .map_err(Error::CouldNotCreateDaemon) - .expect("Failed create daemon"); - - // Start the corresponinding console thread - let read_handle = if backend == BackendType::Nested { - VhostUserConsoleBackend::start_console_thread(&vu_console_backend) - } else { - VhostUserConsoleBackend::start_tcp_console_thread( - &vu_console_backend, - tcp_addr.clone(), - ) - }; - - // Kill console input thread - vu_console_backend.read().unwrap().kill_console_thread(); - - // Wait for read thread to exit - assert_matches!(read_handle.join(), Ok(_)); + start_backend_server(socket, tcp_addr.to_string(), backend)?; } + Ok(()) } + #[test] - fn test_start_net_backend_success() { + fn test_start_backend_server_success() { let args = ConsoleArgs { - socket_path: String::from("/tmp/vhost.sock").into(), + socket_path: String::from("/not_a_dir/vhost.sock").into(), backend: BackendType::Network, tcp_port: String::from("12345"), socket_count: 1, }; - test_backend_start_and_stop(args); + assert!(test_backend_start_and_stop(args).is_err()); } #[test] - fn test_start_nested_backend_success() { - let args = ConsoleArgs { - socket_path: String::from("/tmp/vhost.sock").into(), - backend: BackendType::Nested, - tcp_port: String::from("12345"), + fn test_start_backend_success() { + let config = VuConsoleConfig { + socket_path: String::from("/not_a_dir/vhost.sock").into(), + backend: BackendType::Network, + tcp_port: String::from("12346"), socket_count: 1, }; - test_backend_start_and_stop(args); + assert!(start_backend(config).is_err()); } } diff --git a/staging/vhost-device-console/src/vhu_console.rs b/staging/vhost-device-console/src/vhu_console.rs index f33e2d19d..3246c96f3 100644 --- a/staging/vhost-device-console/src/vhu_console.rs +++ b/staging/vhost-device-console/src/vhu_console.rs @@ -817,6 +817,7 @@ impl VhostUserBackendMut for VhostUserConsoleBackend { #[cfg(test)] mod tests { use super::*; + use std::io::Cursor; use virtio_bindings::virtio_ring::{VRING_DESC_F_NEXT, VRING_DESC_F_WRITE}; use virtio_queue::{mock::MockSplitQueue, Descriptor, Queue}; use vm_memory::{Bytes, GuestAddress, GuestMemoryAtomic, GuestMemoryMmap}; @@ -839,15 +840,12 @@ mod tests { let mut vu_console_backend = VhostUserConsoleBackend::new(console_controller) .expect("Failed create vhuconsole backend"); - // Artificial memory let mem = GuestMemoryAtomic::new( GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap(), ); - // Update memory vu_console_backend.update_memory(mem.clone()).unwrap(); - // Artificial Vring let vring = VringRwLock::new(mem, 0x1000).unwrap(); vring.set_queue_info(0x100, 0x200, 0x300).unwrap(); vring.set_queue_ready(true); @@ -884,12 +882,10 @@ mod tests { let mut vu_console_backend = VhostUserConsoleBackend::new(console_controller) .expect("Failed create vhuconsole backend"); - // Artificial memory let mem = GuestMemoryAtomic::new( GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap(), ); - // Artificial Vring let vring = VringRwLock::new(mem.clone(), 0x1000).unwrap(); // Empty descriptor chain should be ignored @@ -945,7 +941,6 @@ mod tests { let mut vu_console_backend = VhostUserConsoleBackend::new(console_controller) .expect("Failed create vhuconsole backend"); - // Artificial memory let mem = GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap(); // Test 1: Empty queue @@ -1020,7 +1015,6 @@ mod tests { let mut vu_console_backend = VhostUserConsoleBackend::new(console_controller) .expect("Failed create vhuconsole backend"); - // Artificial memory let mem = GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap(); // Test 1: Empty queue @@ -1071,7 +1065,6 @@ mod tests { let mut vu_console_backend = VhostUserConsoleBackend::new(console_controller) .expect("Failed create vhuconsole backend"); - // Artificial memory & update device's memory let mem = GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap(); let mem_1 = GuestMemoryAtomic::new(mem.clone()); vu_console_backend.update_memory(mem_1.clone()).unwrap(); @@ -1125,7 +1118,6 @@ mod tests { let mut vu_console_backend = VhostUserConsoleBackend::new(console_controller) .expect("Failed create vhuconsole backend"); - // Artificial memory let mem = GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap(); // Test 1: Empty queue @@ -1181,7 +1173,6 @@ mod tests { let mut vu_console_backend = VhostUserConsoleBackend::new(console_controller) .expect("Failed create vhuconsole backend"); - // Artificial memory let mem = GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap(); let desc_chain = build_desc_chain(&mem, 1, vec![0], 0x200); let mem1 = GuestMemoryAtomic::new(mem.clone()); @@ -1223,7 +1214,6 @@ mod tests { let mut vu_console_backend = VhostUserConsoleBackend::new(console_controller) .expect("Failed create vhuconsole backend"); - // Artificial memory let mem = GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap(); // Test 1: Empty queue @@ -1238,7 +1228,8 @@ mod tests { let mem1 = GuestMemoryAtomic::new(mem.clone()); let vring = VringRwLock::new(mem1.clone(), 0x1000).unwrap(); vu_console_backend.update_memory(mem1).unwrap(); - assert!(!vu_console_backend + + assert!(vu_console_backend .process_rx_requests(vec![desc_chain], &vring) .unwrap()); @@ -1248,14 +1239,16 @@ mod tests { let vring = VringRwLock::new(mem1.clone(), 0x1000).unwrap(); vu_console_backend.update_memory(mem1).unwrap(); - let input_buffer = "Hello!".to_string(); - let _ = vu_console_backend.rx_data_fifo.add(input_buffer.clone()); - assert_eq!( - vu_console_backend - .process_rx_requests(vec![desc_chain], &vring) - .unwrap_err(), - Error::DescriptorWriteFailed - ); + let input_buffer = b"Hello!"; + // Add each byte individually to the rx_data_fifo + for &byte in input_buffer.clone().iter() { + let _ = vu_console_backend.rx_data_fifo.add(byte); + } + + // available_data are 0 so min_limit is 0 too + assert!(vu_console_backend + .process_rx_requests(vec![desc_chain], &vring) + .unwrap()); // Test 4: Fill message to the buffer. Everything should work! let desc_chain = build_desc_chain(&mem, 1, vec![VRING_DESC_F_WRITE as u16], 0x200); @@ -1263,8 +1256,10 @@ mod tests { let vring = VringRwLock::new(mem1.clone(), 0x1000).unwrap(); vu_console_backend.update_memory(mem1).unwrap(); - let input_buffer = "Hello!".to_string(); - let _ = vu_console_backend.rx_data_fifo.add(input_buffer.clone()); + let input_buffer = b"Hello!"; + for &byte in input_buffer.clone().iter() { + let _ = vu_console_backend.rx_data_fifo.add(byte); + } assert!(vu_console_backend .process_rx_requests(vec![desc_chain.clone()], &vring) .unwrap()); @@ -1283,37 +1278,90 @@ mod tests { .copied() .collect(); - assert_eq!(String::from_utf8(read_buffer).unwrap(), input_buffer); + assert_eq!(read_buffer, input_buffer); } #[test] - fn test_virtio_console_start_tcp_console_thread() { + fn test_virtio_console_start_nested_console_thread() { let console_controller = Arc::new(RwLock::new(ConsoleController::new(BackendType::Nested))); - let vu_console_backend = Arc::new(RwLock::new( - VhostUserConsoleBackend::new(console_controller) - .expect("Failed create vhuconsole backend"), - )); - let tcp_addr = "127.0.0.1:12345".to_string(); - - let read_handle = VhostUserConsoleBackend::start_tcp_console_thread( - &vu_console_backend, - tcp_addr.clone(), - ); - vu_console_backend.read().unwrap().kill_console_thread(); - assert!(read_handle.join().is_ok()); + let mut vu_console_backend = VhostUserConsoleBackend::new(console_controller) + .expect("Failed create vhuconsole backend"); + + let mem = GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap(); + let mem = GuestMemoryAtomic::new(mem); + vu_console_backend.update_memory(mem.clone()).unwrap(); + let vring = VringRwLock::new(mem, 0x1000).unwrap(); + + let input_data = b"H"; + let cursor = Cursor::new(input_data.clone().to_vec()); + + // Replace stdin with a cursor for testing + vu_console_backend.stdin = Some(Box::new(cursor)); + + vu_console_backend.ready_to_write = true; + assert!(vu_console_backend + .handle_event(KEY_EFD, EventSet::IN, &[vring], 0) + .is_ok()); + + let received_byte = vu_console_backend.rx_data_fifo.peek(); + + // verify that the character has been received and is the one we sent + assert!(received_byte.clone().is_ok()); + assert_eq!(received_byte.unwrap(), input_data[0]); } #[test] - fn test_virtio_console_start_nested_console_thread() { - let console_controller = Arc::new(RwLock::new(ConsoleController::new(BackendType::Nested))); - let vu_console_backend = Arc::new(RwLock::new( - VhostUserConsoleBackend::new(console_controller) - .expect("Failed create vhuconsole backend"), - )); + fn test_virtio_console_tcp_console_read_func() { + let console_controller = + Arc::new(RwLock::new(ConsoleController::new(BackendType::Network))); + let mut vu_console_backend = VhostUserConsoleBackend::new(console_controller) + .expect("Failed create vhuconsole backend"); + + let mem = GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap(); + let mem = GuestMemoryAtomic::new(mem); + vu_console_backend.update_memory(mem.clone()).unwrap(); + let vring = VringRwLock::new(mem, 0x1000).unwrap(); + + let input_data = b"H"; + let cursor = Cursor::new(input_data.clone().to_vec()); + + // Replace stream with a cursor for testing + vu_console_backend.stream = Some(Box::new(cursor)); + + vu_console_backend.ready_to_write = true; + assert!(vu_console_backend + .handle_event(KEY_EFD, EventSet::IN, &[vring], 0) + .is_ok()); + + let received_byte = vu_console_backend.rx_data_fifo.peek(); - let read_handle = VhostUserConsoleBackend::start_console_thread(&vu_console_backend); + // verify that the character has been received and is the one we sent + assert!(received_byte.clone().is_ok()); + assert_eq!(received_byte.unwrap(), input_data[0]); + } + + #[test] + fn test_virtio_console_tcp_console_write_func() { + let console_controller = + Arc::new(RwLock::new(ConsoleController::new(BackendType::Network))); + let mut vu_console_backend = VhostUserConsoleBackend::new(console_controller) + .expect("Failed create vhuconsole backend"); + + let mem = GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap(); + let mem = GuestMemoryAtomic::new(mem); + vu_console_backend.update_memory(mem.clone()).unwrap(); + + // Test 1: Call the actual read function + let cursor = Cursor::new(Vec::new()); + + vu_console_backend.stream = Some(Box::new(cursor)); + vu_console_backend + .output_queue + .add("Test".to_string()) + .unwrap(); + vu_console_backend.write_tcp_stream(); - vu_console_backend.read().unwrap().kill_console_thread(); - assert!(read_handle.join().is_ok()); + // All data has been consumed by the cursor + assert_eq!(vu_console_backend.output_queue.size(), 0); } } From 76053f1608d99f647096aadee31bd38f064214e0 Mon Sep 17 00:00:00 2001 From: Timos Ampelikiotis Date: Thu, 10 Oct 2024 11:50:11 +0300 Subject: [PATCH 3/3] vhost-device-console: promote to main workspace The current implementation seems ready to be promoted to the main workspace since the device supports test coverage have been increased and the basic functionality is implemented. Cargo.lock files both in main and staging workspaces are updated. Support for VIRTIO_CONSOLE_F_SIZE and VIRTIO_CONSOLE_F_EMERG_WRITE is still missing. Signed-off-by: Timos Ampelikiotis --- Cargo.lock | 259 ++++++++++++++---- Cargo.toml | 1 + README.md | 2 +- staging/Cargo.lock | 199 +------------- staging/Cargo.toml | 1 - staging/coverage_config_x86_64.json | 2 +- .../CHANGELOG.md | 0 .../Cargo.toml | 0 .../LICENSE-APACHE | 0 .../LICENSE-BSD-3-Clause | 0 .../README.md | 6 - .../src/backend.rs | 0 .../src/console.rs | 0 .../src/main.rs | 0 .../src/vhu_console.rs | 0 .../src/virtio_console.rs | 0 16 files changed, 225 insertions(+), 245 deletions(-) rename {staging/vhost-device-console => vhost-device-console}/CHANGELOG.md (100%) rename {staging/vhost-device-console => vhost-device-console}/Cargo.toml (100%) rename {staging/vhost-device-console => vhost-device-console}/LICENSE-APACHE (100%) rename {staging/vhost-device-console => vhost-device-console}/LICENSE-BSD-3-Clause (100%) rename {staging/vhost-device-console => vhost-device-console}/README.md (95%) rename {staging/vhost-device-console => vhost-device-console}/src/backend.rs (100%) rename {staging/vhost-device-console => vhost-device-console}/src/console.rs (100%) rename {staging/vhost-device-console => vhost-device-console}/src/main.rs (100%) rename {staging/vhost-device-console => vhost-device-console}/src/vhu_console.rs (100%) rename {staging/vhost-device-console => vhost-device-console}/src/virtio_console.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index 7af0779d8..315550f9d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -45,9 +45,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.17" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23a1e53f0f5d86382dafe1cf314783b2044280f406e7e1506368220ad11b1338" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", @@ -94,9 +94,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.92" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74f37166d7d48a0284b99dd824694c26119c700b53bf0d1540cdb147dbdaaf13" +checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" [[package]] name = "arc-swap" @@ -149,9 +149,9 @@ dependencies = [ [[package]] name = "bindgen" -version = "0.69.4" +version = "0.69.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" +checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" dependencies = [ "annotate-snippets", "bitflags 2.6.0", @@ -206,9 +206,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.1.34" +version = "1.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b9470d453346108f93a59222a9a1a5724db32d0a4727b7ab7ace4b4d822dc9" +checksum = "baee610e9452a8f6f0a1b6194ec09ff9e2d85dea54432acdae41aa0761c95d70" dependencies = [ "shlex", ] @@ -301,6 +301,19 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +[[package]] +name = "console" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "unicode-width", + "windows-sys 0.52.0", +] + [[package]] name = "convert_case" version = "0.6.0" @@ -319,12 +332,43 @@ dependencies = [ "futures", ] +[[package]] +name = "crossterm" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" +dependencies = [ + "bitflags 2.6.0", + "crossterm_winapi", + "mio", + "parking_lot", + "rustix", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + [[package]] name = "either" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + [[package]] name = "env_filter" version = "0.1.2" @@ -377,9 +421,9 @@ dependencies = [ [[package]] name = "errno" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys 0.52.0", @@ -547,9 +591,9 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "hashbrown" -version = "0.15.0" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" +checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" [[package]] name = "heck" @@ -562,15 +606,15 @@ dependencies = [ [[package]] name = "heck" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] -name = "heck" -version = "0.5.0" +name = "hermit-abi" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "home" @@ -653,9 +697,9 @@ checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" [[package]] name = "libgpiod" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f726cc57967d3b982e1b3074cdfa8f68a3b36f345fd80b5c0ccb0dfd7dee0f79" +checksum = "3229b4b7a782facc3aef8d7e1c900de7c860f7c2b5c94c19880b84b5c400d3c8" dependencies = [ "errno 0.2.8", "intmap", @@ -698,7 +742,7 @@ dependencies = [ "libspa-sys", "nix 0.27.1", "nom", - "system-deps 6.2.0", + "system-deps 6.2.2", ] [[package]] @@ -707,9 +751,9 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf0d9716420364790e85cbb9d3ac2c950bde16a7dd36f3209b7dfdfc4a24d01f" dependencies = [ - "bindgen 0.69.4", + "bindgen 0.69.5", "cc", - "system-deps 6.2.0", + "system-deps 6.2.2", ] [[package]] @@ -718,6 +762,16 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" version = "0.4.22" @@ -754,6 +808,19 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "mio" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +dependencies = [ + "hermit-abi", + "libc", + "log", + "wasi", + "windows-sys 0.52.0", +] + [[package]] name = "nix" version = "0.23.2" @@ -828,6 +895,29 @@ version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + [[package]] name = "peeking_take_while" version = "0.1.2" @@ -869,9 +959,9 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "849e188f90b1dda88fe2bfe1ad31fe5f158af2c98f80fb5d13726c44f3f01112" dependencies = [ - "bindgen 0.69.4", + "bindgen 0.69.5", "libspa-sys", - "system-deps 6.2.0", + "system-deps 6.2.2", ] [[package]] @@ -891,11 +981,10 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "2.0.2" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" dependencies = [ - "toml_datetime", "toml_edit", ] @@ -908,6 +997,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "queues" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1475abae4f8ad4998590fe3acfe20104f0a5d48fc420c817cd2c09c3f56151f0" + [[package]] name = "quote" version = "1.0.37" @@ -953,6 +1048,15 @@ dependencies = [ "getrandom", ] +[[package]] +name = "redox_syscall" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +dependencies = [ + "bitflags 2.6.0", +] + [[package]] name = "regex" version = "1.11.1" @@ -1034,12 +1138,12 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.38" +version = "0.38.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa260229e6538e52293eeb577aabd09945a09d6d9cc0fc550ed7529056c2e32a" +checksum = "375116bee2be9ed569afe2154ea6a99dfdffd257f533f187498c2a8f5feaf4ee" dependencies = [ "bitflags 2.6.0", - "errno 0.3.8", + "errno 0.3.9", "libc", "linux-raw-sys", "windows-sys 0.52.0", @@ -1051,6 +1155,12 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "semver" version = "1.0.23" @@ -1105,6 +1215,36 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + [[package]] name = "slab" version = "0.4.9" @@ -1183,15 +1323,15 @@ dependencies = [ [[package]] name = "system-deps" -version = "6.2.0" +version = "6.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2d580ff6a20c55dfb86be5f9c238f67835d0e81cbdea8bf5680e0897320331" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" dependencies = [ "cfg-expr", - "heck 0.4.1", + "heck 0.5.0", "pkg-config", - "toml 0.8.2", - "version-compare 0.1.1", + "toml 0.8.19", + "version-compare 0.2.0", ] [[package]] @@ -1221,18 +1361,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.67" +version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3c6efbfc763e64eb85c11c25320f0737cb7364c4b6336db90aa9ebe27a0bbd" +checksum = "02dd99dc800bbb97186339685293e1cc5d9df1f8fae2d0aecd9ff1c77efea892" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.67" +version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b607164372e89797d78b8e23a6d67d5d1038c1c65efd52e1389ef8b77caba2a6" +checksum = "a7c61ec9a6f64d2793d8a45faba21efbe3ced62a886d44c36a009b2b519b4c7e" dependencies = [ "proc-macro2", "quote", @@ -1250,9 +1390,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.2" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" dependencies = [ "serde", "serde_spanned", @@ -1262,18 +1402,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.3" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.20.2" +version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ "indexmap", "serde", @@ -1351,9 +1491,9 @@ checksum = "1c18c859eead79d8b95d09e4678566e8d70105c4e7b251f707a03df32442661b" [[package]] name = "version-compare" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29" +checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" [[package]] name = "version_check" @@ -1374,6 +1514,27 @@ dependencies = [ "vmm-sys-util", ] +[[package]] +name = "vhost-device-console" +version = "0.1.0" +dependencies = [ + "assert_matches", + "clap", + "console", + "crossterm", + "env_logger", + "epoll", + "log", + "queues", + "thiserror", + "vhost", + "vhost-user-backend", + "virtio-bindings", + "virtio-queue", + "vm-memory", + "vmm-sys-util", +] + [[package]] name = "vhost-device-gpio" version = "0.1.0" @@ -1777,9 +1938,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.5.40" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" dependencies = [ "memchr", ] diff --git a/Cargo.toml b/Cargo.toml index b0d010ddd..33924eb81 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ resolver = "2" members = [ + "vhost-device-console", "vhost-device-gpio", "vhost-device-i2c", "vhost-device-input", diff --git a/README.md b/README.md index b4cfe9771..e2b042949 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ To be included here device backends must: Here is the list of device backends that we support: +- [Console](https://github.com/rust-vmm/vhost-device/blob/main/vhost-device-console/README.md) - [GPIO](https://github.com/rust-vmm/vhost-device/blob/main/vhost-device-gpio/README.md) - [I2C](https://github.com/rust-vmm/vhost-device/blob/main/vhost-device-i2c/README.md) - [Input](https://github.com/rust-vmm/vhost-device/blob/main/vhost-device-input/README.md) @@ -48,7 +49,6 @@ Here is the list of device backends in **staging**: - [Video](https://github.com/rust-vmm/vhost-device/blob/main/staging/vhost-device-video/README.md) - [Can](https://github.com/rust-vmm/vhost-device/blob/main/staging/vhost-device-can/README.md) -- [Console](https://github.com/rust-vmm/vhost-device/blob/main/staging/vhost-device-console/README.md)