Alioth Code Coverage

vu.rs0.00%

1// Copyright 2025 Google LLC
2//
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 at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// 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 and
13// limitations under the License.
14
15use std::marker::PhantomData;
16use std::os::unix::net::UnixListener;
17use std::path::Path;
18use std::sync::Arc;
19
20use alioth::errors::{DebugTrace, trace_error};
21use alioth::mem::mapped::RamBus;
22use alioth::virtio::dev::blk::BlkFileParam;
23use alioth::virtio::dev::fs::shared_dir::SharedDirParam;
24use alioth::virtio::dev::net::tap::NetTapParam;
25use alioth::virtio::dev::vsock::UdsVsockParam;
26use alioth::virtio::dev::{DevParam, Virtio, VirtioDevice};
27use alioth::virtio::vu::backend::{VuBackend, VuEventfd, VuIrqSender};
28use clap::{Args, Subcommand};
29use serde::Deserialize;
30use serde_aco::{Help, help_text};
31use snafu::{ResultExt, Snafu};
32
33use crate::objects::{DOC_OBJECTS, parse_objects};
34
35#[trace_error]
36#[derive(Snafu, DebugTrace)]
37#[snafu(module, context(suffix(false)))]
38pub 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
65fn phantom_parser<T>(_: &str) -> Result<PhantomData<T>, &'static str> {
66 Ok(PhantomData)
67}
68
69#[derive(Args, Debug, Clone)]
70pub struct DevArgs<T>
71where
72 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)]
85pub 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)]
98pub 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
107fn create_dev<D, P>(
108 name: String,
109 args: &DevArgs<P>,
110 memory: Arc<RamBus>,
111) -> Result<VirtioDevice<VuIrqSender, VuEventfd>, Error>
112where
113 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
125fn 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
134pub 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