template: split into main.rs and lib.rs

Split functionality between library crate and an executable crate that
consumes the library.

Signed-off-by: Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
This commit is contained in:
Manos Pitsidianakis 2025-10-07 11:56:52 +03:00 committed by Manos Pitsidianakis
parent 37e527045a
commit 6ec4e6eb29
3 changed files with 116 additions and 99 deletions

View File

@ -5,10 +5,7 @@
//
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
use std::{
convert,
io::{self, Result as IoResult},
};
use std::io::Result as IoResult;
use log::{info, warn};
use thiserror::Error as ThisError;
@ -34,7 +31,7 @@ type Result<T> = std::result::Result<T, Error>;
#[derive(Copy, Clone, Debug, Eq, PartialEq, ThisError)]
/// Errors related to vhost-device-foo daemon.
pub(crate) enum Error {
pub enum Error {
#[error("Failed to handle event, didn't match EPOLLIN")]
HandleEventNotEpollIn,
#[error("Failed to handle unknown event")]
@ -47,13 +44,13 @@ pub(crate) enum Error {
EventFdFailed,
}
impl convert::From<Error> for io::Error {
impl From<Error> for std::io::Error {
fn from(e: Error) -> Self {
io::Error::other(e)
std::io::Error::other(e)
}
}
pub(crate) struct VhostUserFooBackend {
pub struct VhostUserFooBackend {
info: FooInfo,
event_idx: bool,
pub exit_event: EventFd,
@ -469,7 +466,7 @@ mod tests {
.handle_event(0, EventSet::OUT, &[vring.clone()], 0)
.unwrap_err()
.kind(),
io::ErrorKind::Other
std::io::ErrorKind::Other
);
assert_eq!(
@ -477,7 +474,7 @@ mod tests {
.handle_event(1, EventSet::IN, &[vring.clone()], 0)
.unwrap_err()
.kind(),
io::ErrorKind::Other
std::io::ErrorKind::Other
);
// Hit the loop part

View File

@ -0,0 +1,104 @@
// VIRTIO FOO Emulation via vhost-user
//
// Copyright 2023 Linaro Ltd. All Rights Reserved.
// Viresh Kumar <viresh.kumar@linaro.org>
//
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
pub mod backend;
use std::{
path::PathBuf,
sync::{Arc, RwLock},
thread::{spawn, JoinHandle},
};
use backend::VhostUserFooBackend;
use log::error;
use thiserror::Error as ThisError;
use vhost_user_backend::VhostUserDaemon;
use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap};
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, ThisError)]
/// Errors related to low level foo helpers
pub enum Error {
#[error("Could not create backend: {0}")]
CouldNotCreateBackend(backend::Error),
#[error("Could not create daemon: {0}")]
CouldNotCreateDaemon(vhost_user_backend::Error),
#[error("Fatal error: {0}")]
ServeFailed(vhost_user_backend::Error),
}
#[derive(PartialEq, Debug)]
pub struct FooConfiguration {
pub socket_path: PathBuf,
}
#[derive(Copy, Clone)]
pub struct FooInfo {
counter: u32,
}
impl FooInfo {
pub fn new() -> Self {
Self { counter: 0 }
}
pub fn counter(&mut self) -> u32 {
self.counter += 1;
self.counter
}
}
impl Default for FooInfo {
fn default() -> Self {
Self::new()
}
}
pub fn start_backend(config: FooConfiguration) -> Result<()> {
let socket_path = config.socket_path.clone();
let info = FooInfo::new();
let handle: JoinHandle<Result<()>> = spawn(move || loop {
// There isn't much value in complicating code here to return an error from the
// threads, and so the code uses unwrap() instead. The panic on a thread
// won't cause trouble to the main() function and should be safe for the
// daemon.
let backend = Arc::new(RwLock::new(
VhostUserFooBackend::new(info).map_err(Error::CouldNotCreateBackend)?,
));
let mut daemon = VhostUserDaemon::new(
String::from("vhost-device-template-backend"),
backend,
GuestMemoryAtomic::new(GuestMemoryMmap::new()),
)
.map_err(Error::CouldNotCreateDaemon)?;
daemon.serve(&socket_path).map_err(Error::ServeFailed)?;
});
handle.join().map_err(std::panic::resume_unwind).unwrap()
}
#[cfg(test)]
mod tests {
use std::path::Path;
use assert_matches::assert_matches;
use super::*;
#[test]
fn test_fail_listener() {
// This will fail the listeners and thread will panic.
let socket_path = Path::new("/proc/foo/path/not/present").to_path_buf();
let cmd_args = FooConfiguration { socket_path };
assert_matches!(start_backend(cmd_args).unwrap_err(), Error::ServeFailed(_));
}
}

View File

@ -5,35 +5,10 @@
//
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
mod vhu_foo;
use std::{
path::PathBuf,
process::exit,
sync::{Arc, RwLock},
thread::{spawn, JoinHandle},
};
use std::{path::PathBuf, process::exit};
use clap::Parser;
use log::error;
use thiserror::Error as ThisError;
use vhost_user_backend::VhostUserDaemon;
use vhu_foo::VhostUserFooBackend;
use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap};
type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, ThisError)]
/// Errors related to low level foo helpers
pub(crate) enum Error {
#[error("Could not create backend: {0}")]
CouldNotCreateBackend(vhu_foo::Error),
#[error("Could not create daemon: {0}")]
CouldNotCreateDaemon(vhost_user_backend::Error),
#[error("Fatal error: {0}")]
ServeFailed(vhost_user_backend::Error),
}
use vhost_device_template::{start_backend, Error, FooConfiguration, Result};
#[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None)]
struct FooArgs {
@ -42,11 +17,6 @@ struct FooArgs {
socket_path: PathBuf,
}
#[derive(PartialEq, Debug)]
struct FooConfiguration {
socket_path: PathBuf,
}
impl TryFrom<FooArgs> for FooConfiguration {
type Error = Error;
@ -61,55 +31,12 @@ impl TryFrom<FooArgs> for FooConfiguration {
}
}
#[derive(Copy, Clone)]
pub(crate) struct FooInfo {
counter: u32,
}
impl FooInfo {
pub fn new() -> Self {
Self { counter: 0 }
}
pub fn counter(&mut self) -> u32 {
self.counter += 1;
self.counter
}
}
fn start_backend(args: FooArgs) -> Result<()> {
let config = FooConfiguration::try_from(args).unwrap();
let socket_path = config.socket_path.clone();
let info = FooInfo::new();
let handle: JoinHandle<Result<()>> = spawn(move || loop {
// There isn't much value in complicating code here to return an error from the
// threads, and so the code uses unwrap() instead. The panic on a thread
// won't cause trouble to the main() function and should be safe for the
// daemon.
let backend = Arc::new(RwLock::new(
VhostUserFooBackend::new(info).map_err(Error::CouldNotCreateBackend)?,
));
let mut daemon = VhostUserDaemon::new(
String::from("vhost-device-template-backend"),
backend,
GuestMemoryAtomic::new(GuestMemoryMmap::new()),
)
.map_err(Error::CouldNotCreateDaemon)?;
daemon.serve(&socket_path).map_err(Error::ServeFailed)?;
});
handle.join().map_err(std::panic::resume_unwind).unwrap()
}
fn main() {
env_logger::init();
if let Err(e) = start_backend(FooArgs::parse()) {
error!("{e}");
let config = FooConfiguration::try_from(FooArgs::parse()).unwrap();
if let Err(e) = start_backend(config) {
log::error!("{e}");
exit(1);
}
}
@ -118,8 +45,6 @@ fn main() {
mod tests {
use std::path::Path;
use assert_matches::assert_matches;
use super::*;
impl FooArgs {
@ -143,13 +68,4 @@ mod tests {
assert_eq!(config, expected_config);
}
#[test]
fn test_fail_listener() {
// This will fail the listeners and thread will panic.
let socket_name = Path::new("~/path/not/present/foo");
let cmd_args = FooArgs::from_args(socket_name);
assert_matches!(start_backend(cmd_args).unwrap_err(), Error::ServeFailed(_));
}
}