vu.rs0.00%
1
// Copyright 2025 Google LLC2
//3
// Licensed under the Apache License, Version 2.0 (the "License");4
// you may not use this file except in compliance with the License.5
// You may obtain a copy of the License at6
//7
// https://www.apache.org/licenses/LICENSE-2.08
//9
// Unless required by applicable law or agreed to in writing, software10
// distributed under the License is distributed on an "AS IS" BASIS,11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.12
// See the License for the specific language governing permissions and13
// limitations under the License.14
15
use std::marker::PhantomData;16
use std::os::unix::net::UnixListener;17
use std::path::Path;18
use std::sync::Arc;19
20
use alioth::errors::{DebugTrace, trace_error};21
use alioth::mem::mapped::RamBus;22
use alioth::virtio::dev::blk::BlkFileParam;23
use alioth::virtio::dev::fs::shared_dir::SharedDirParam;24
use alioth::virtio::dev::net::tap::NetTapParam;25
use alioth::virtio::dev::vsock::UdsVsockParam;26
use alioth::virtio::dev::{DevParam, Virtio, VirtioDevice};27
use alioth::virtio::vu::backend::{VuBackend, VuEventfd, VuIrqSender};28
use clap::{Args, Subcommand};29
use serde::Deserialize;30
use serde_aco::{Help, help_text};31
use snafu::{ResultExt, Snafu};32
33
use crate::objects::{DOC_OBJECTS, parse_objects};34
35
#[trace_error]36
#[derive(Snafu, DebugTrace)]37
#[snafu(module, context(suffix(false)))]38
pub enum Error {39
#[snafu(display("Failed to parse {arg}"))]40
ParseArg {41
arg: String,42
error: serde_aco::Error,43
},44
#[snafu(display("Failed to parse objects"), context(false))]45
ParseObjects { source: crate::objects::Error },46
#[snafu(display("Failed to bind socket {socket:?}"))]47
Bind {48
socket: Box<Path>,49
error: std::io::Error,50
},51
#[snafu(display("Failed to accept connections"))]52
Accept { error: std::io::Error },53
#[snafu(display("Failed to create a VirtIO device"))]54
CreateVirtio { source: alioth::virtio::Error },55
#[snafu(display("Failed to create a vhost-user backend"))]56
CreateVu {57
source: alioth::virtio::vu::backend::Error,58
},59
#[snafu(display("vhost-user device runtime error"))]60
Runtime {61
source: alioth::virtio::vu::backend::Error,62
},63
}64
65
fn phantom_parser<T>(_: &str) -> Result<PhantomData<T>, &'static str> {66
Ok(PhantomData)67
}68
69
#[derive(Args, Debug, Clone)]70
pub struct DevArgs<T>71
where72
T: Help + Send + Sync + 'static,73
{74
#[arg(short, long, value_name("PARAM"), help(help_text::<T>("Specify device parameters.")))]75
pub param: String,76
77
#[arg(short, long("object"), help(DOC_OBJECTS), value_name("OBJECT"))]78
pub objects: Vec<String>,79
80
#[arg(hide(true), value_parser(phantom_parser::<T>), default_value(""))]81
pub phantom: PhantomData<T>,82
}83
84
#[derive(Subcommand, Debug, Clone)]85
pub enum DevType {86
/// VirtIO net device backed by TUN/TAP, MacVTap, or IPVTap.87
Net(DevArgs<NetTapParam>),88
/// VirtIO block device backed by a file.89
Blk(DevArgs<BlkFileParam>),90
/// VirtIO filesystem device backed by a shared host directory.91
Fs(DevArgs<SharedDirParam>),92
/// VirtIO vsock device backed by a Unix domain socket.93
Vsock(DevArgs<UdsVsockParam>),94
}95
96
#[derive(Args, Debug, Clone)]97
#[command(arg_required_else_help = true)]98
pub struct VuArgs {99
/// Path to a Unix domain socket to listen on.100
#[arg(short, long, value_name = "PATH")]101
pub socket: Box<Path>,102
103
#[command(subcommand)]104
pub ty: DevType,105
}106
107
fn create_dev<D, P>(108
name: String,109
args: &DevArgs<P>,110
memory: Arc<RamBus>,111
) -> Result<VirtioDevice<VuIrqSender, VuEventfd>, Error>112
where113
D: Virtio,114
P: DevParam<Device = D> + Help + for<'a> Deserialize<'a> + Send + Sync + 'static,115
{116
let name: Arc<str> = name.into();117
let objects = parse_objects(&args.objects)?;118
let param: P = serde_aco::from_args(&args.param, &objects)119
.context(error::ParseArg { arg: &args.param })?;120
let dev = param.build(name.clone()).context(error::CreateVirtio)?;121
let dev = VirtioDevice::new(name, dev, memory, false).context(error::CreateVirtio)?;122
Ok(dev)123
}124
125
fn run_backend(mut backend: VuBackend) {126
let r = backend.run().context(error::Runtime);127
let name = backend.name();128
match r {129
Ok(()) => log::info!("{name}: done"),130
Err(e) => log::error!("{name}: {e:?}"),131
}132
}133
134
pub fn start(args: VuArgs) -> Result<(), Error> {135
let VuArgs { socket, ty } = args;136
let listener = UnixListener::bind(&socket).context(error::Bind { socket })?;137
let mut index = 0i32;138
loop {139
let memory = Arc::new(RamBus::new());140
let dev = match &ty {141
DevType::Net(args) => create_dev(format!("net-{index}"), args, memory.clone()),142
DevType::Blk(args) => create_dev(format!("blk-{index}"), args, memory.clone()),143
DevType::Fs(args) => create_dev(format!("fs-{index}"), args, memory.clone()),144
DevType::Vsock(args) => create_dev(format!("vsock-{index}"), args, memory.clone()),145
}?;146
let (conn, _) = listener.accept().context(error::Accept)?;147
let backend = VuBackend::new(conn, dev, memory).context(error::CreateVu)?;148
run_backend(backend);149
index = index.wrapping_add(1);150
}151
}152