• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

google / alioth / 21500373191

30 Jan 2026 12:54AM UTC coverage: 26.439% (-0.004%) from 26.443%
21500373191

Pull #376

github

web-flow
Merge a0e08d096 into f3a29f6dd
Pull Request #376: refactor(vm): move vm Config to cli crate

429 of 1146 branches covered (37.43%)

Branch coverage included in aggregate %.

0 of 72 new or added lines in 2 files covered. (0.0%)

1 existing line in 1 file now uncovered.

3608 of 14123 relevant lines covered (25.55%)

23.04 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

58.33
/alioth-cli/src/boot/boot.rs
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

15
mod config;
16

17
use std::collections::HashMap;
18
use std::ffi::CString;
19
use std::mem;
20
use std::path::{Path, PathBuf};
21

22
use alioth::board::{BoardConfig, CpuConfig};
23
#[cfg(target_arch = "x86_64")]
24
use alioth::device::fw_cfg::FwCfgItemParam;
25
use alioth::errors::{DebugTrace, trace_error};
26
#[cfg(target_os = "macos")]
27
use alioth::hv::Hvf;
28
#[cfg(target_os = "linux")]
29
use alioth::hv::Kvm;
30
use alioth::hv::{Coco, HvConfig, Hypervisor};
31
use alioth::loader::{Executable, Payload};
32
use alioth::mem::{MemBackend, MemConfig};
33
#[cfg(target_os = "linux")]
34
use alioth::vfio::{CdevParam, ContainerParam, GroupParam, IoasParam};
35
#[cfg(target_os = "linux")]
36
use alioth::virtio::DeviceId;
37
use alioth::virtio::dev::balloon::BalloonParam;
38
use alioth::virtio::dev::blk::BlkFileParam;
39
use alioth::virtio::dev::entropy::EntropyParam;
40
#[cfg(target_os = "linux")]
41
use alioth::virtio::vu::frontend::VuFrontendParam;
42
use alioth::virtio::worker::WorkerApi;
43
use alioth::vm::Machine;
44
use clap::Args;
45
use serde_aco::help_text;
46
use snafu::{ResultExt, Snafu};
47

48
use crate::objects::{DOC_OBJECTS, parse_objects};
49

50
use self::config::{BlkParam, Config, FsParam, NetParam, VsockParam};
51

52
#[trace_error]
53
#[derive(Snafu, DebugTrace)]
×
54
#[snafu(module, context(suffix(false)))]
55
pub enum Error {
56
    #[snafu(display("Failed to parse {arg}"))]
57
    ParseArg {
58
        arg: String,
59
        error: serde_aco::Error,
60
    },
61
    #[snafu(display("Failed to parse objects"), context(false))]
62
    ParseObjects { source: crate::objects::Error },
63
    #[cfg(target_os = "linux")]
64
    #[snafu(display("Failed to access system hypervisor"))]
65
    Hypervisor { source: alioth::hv::Error },
66
    #[snafu(display("Failed to create a VM"))]
67
    CreateVm { source: alioth::vm::Error },
68
    #[snafu(display("Failed to boot a VM"))]
69
    BootVm { source: alioth::vm::Error },
70
    #[snafu(display("VM did not shutdown peacefully"))]
71
    WaitVm { source: alioth::vm::Error },
72
}
73

74
#[derive(Args, Debug, Clone, Default)]
75
#[command(arg_required_else_help = true, alias("run"))]
76
pub struct BootArgs {
77
    #[arg(long, help(
78
        help_text::<HvConfig>("Specify the Hypervisor to run on.")
79
    ), value_name = "HV")]
80
    hypervisor: Option<String>,
81

82
    /// Path to a Linux kernel image.
83
    #[arg(short, long, value_name = "PATH")]
84
    kernel: Option<Box<Path>>,
85

86
    /// Path to an ELF kernel with PVH note.
87
    #[cfg(target_arch = "x86_64")]
88
    #[arg(long, value_name = "PATH")]
89
    pvh: Option<Box<Path>>,
90

91
    /// Path to a firmware image.
92
    #[arg(long, short, value_name = "PATH")]
93
    firmware: Option<Box<Path>>,
94

95
    /// Command line to pass to the kernel, e.g. `console=ttyS0`.
96
    #[arg(short, long, alias = "cmd-line", value_name = "ARGS")]
97
    cmdline: Option<CString>,
98

99
    /// Path to an initramfs image.
100
    #[arg(short, long, value_name = "PATH")]
101
    initramfs: Option<Box<Path>>,
102

103
    /// DEPRECATED: Use --cpu instead.
104
    #[arg(long, default_value_t = 1)]
105
    num_cpu: u16,
106

107
    #[arg(short('p'), long, help(
108
        help_text::<CpuConfig>("Configure the VCPUs of the guest.")
109
    ))]
110
    cpu: Option<Box<str>>,
111

112
    /// DEPRECATED: Use --memory instead.
113
    #[arg(long, default_value = "1G")]
114
    mem_size: String,
115

116
    #[arg(short, long, help(
117
        help_text::<MemConfig>("Specify the memory of the guest.")
118
    ))]
119
    memory: Option<String>,
120

121
    /// Add a pvpanic device.
122
    #[arg(long)]
123
    pvpanic: bool,
124

125
    #[cfg(target_arch = "x86_64")]
126
    #[arg(long, help(
127
        help_text::<FwCfgItemParam>("Add an extra item to the fw_cfg device.")
128
    ), value_name = "ITEM")]
129
    fw_cfg: Vec<String>,
130

131
    /// Add a VirtIO entropy device.
132
    #[arg(long)]
133
    entropy: bool,
134

135
    #[arg(long, help(
136
        help_text::<NetParam>("Add a VirtIO net device.")
137
    ))]
138
    net: Vec<String>,
139

140
    #[arg(long, help(
141
        help_text::<BlkParam>("Add a VirtIO block device.")
142
    ))]
143
    blk: Vec<String>,
144

145
    #[arg(long, help(
146
        help_text::<Coco>("Enable confidential compute supported by host platform.")
147
    ))]
148
    coco: Option<String>,
149

150
    #[arg(long, help(
151
        help_text::<FsParam>("Add a VirtIO filesystem device.")
152
    ))]
153
    fs: Vec<String>,
154

155
    #[arg(long, help(
156
        help_text::<VsockParam>("Add a VirtIO vsock device.")
157
    ))]
158
    vsock: Option<String>,
159

160
    #[cfg(target_os = "linux")]
161
    #[arg(long, help(help_text::<CdevParam>(
162
        "Assign a host PCI device to the guest using IOMMUFD API."
163
    ) ))]
164
    vfio_cdev: Vec<String>,
165

166
    #[cfg(target_os = "linux")]
167
    #[arg(long, help(help_text::<IoasParam>("Create a new IO address space.")))]
168
    vfio_ioas: Vec<String>,
169

170
    #[cfg(target_os = "linux")]
171
    #[arg(long, help(help_text::<GroupParam>(
172
        "Assign a host PCI device to the guest using legacy VFIO API."
173
    )))]
174
    vfio_group: Vec<String>,
175

176
    #[cfg(target_os = "linux")]
177
    #[arg(long, help(help_text::<ContainerParam>("Add a new VFIO container.")))]
178
    vfio_container: Vec<String>,
179

180
    #[arg(long)]
181
    #[arg(long, help(help_text::<BalloonParam>("Add a VirtIO balloon device.")))]
182
    balloon: Option<String>,
183

184
    #[arg(short, long("object"), help = DOC_OBJECTS, value_name = "OBJECT")]
185
    objects: Vec<String>,
186
}
187

188
fn parse_net_arg(arg: &str, objects: &HashMap<&str, &str>) -> serde_aco::Result<NetParam> {
8✔
189
    #[cfg(target_os = "linux")]
190
    if let Ok(param) = serde_aco::from_args(arg, objects) {
6✔
191
        Ok(param)
2✔
192
    } else {
193
        let param = serde_aco::from_args(arg, objects)?;
4✔
194
        Ok(NetParam::Tap(param))
4✔
195
    }
196

197
    #[cfg(target_os = "macos")]
198
    serde_aco::from_args(arg, objects)
2✔
199
}
8✔
200

201
fn parse_blk_arg(arg: &str, objects: &HashMap<&str, &str>) -> BlkParam {
17✔
202
    if let Ok(param) = serde_aco::from_args(arg, objects) {
17✔
203
        param
11✔
204
    } else if let Ok(param) = serde_aco::from_args(arg, objects) {
6✔
205
        BlkParam::File(param)
3✔
206
    } else {
207
        eprintln!("Please update the cmd line to --blk file,path={arg}");
3✔
208
        BlkParam::File(BlkFileParam {
3✔
209
            path: PathBuf::from(arg).into(),
3✔
210
            readonly: false,
3✔
211
            api: WorkerApi::Mio,
3✔
212
        })
3✔
213
    }
214
}
17✔
215

216
fn parse_mem_arg(
8✔
217
    arg: Option<String>,
8✔
218
    mem_size: String,
8✔
219
    objects: &HashMap<&str, &str>,
8✔
220
) -> Result<MemConfig, Error> {
8✔
221
    let config = if let Some(arg) = arg {
8✔
222
        serde_aco::from_args(&arg, objects).context(error::ParseArg { arg })?
5✔
223
    } else {
224
        #[cfg(target_os = "linux")]
225
        eprintln!("Please update the cmd line to --memory size={mem_size},backend=memfd");
2✔
226
        MemConfig {
227
            size: serde_aco::from_args(&mem_size, objects)
3✔
228
                .context(error::ParseArg { arg: mem_size })?,
3✔
229
            #[cfg(target_os = "linux")]
230
            backend: MemBackend::Memfd,
2✔
231
            #[cfg(not(target_os = "linux"))]
232
            backend: MemBackend::Anonymous,
1✔
233
            ..Default::default()
3✔
234
        }
235
    };
236
    Ok(config)
8✔
237
}
8✔
238

239
fn parse_cpu_arg(
9✔
240
    arg: Option<Box<str>>,
9✔
241
    num_cpu: u16,
9✔
242
    objects: &HashMap<&str, &str>,
9✔
243
) -> Result<CpuConfig, Error> {
9✔
244
    let config = if let Some(arg) = arg {
9✔
245
        serde_aco::from_args(&arg, objects).context(error::ParseArg { arg })?
6✔
246
    } else {
247
        eprintln!("Please update the cmd line to --cpu count={num_cpu}");
3✔
248
        CpuConfig {
3✔
249
            count: num_cpu,
3✔
250
            ..Default::default()
3✔
251
        }
3✔
252
    };
253
    Ok(config)
9✔
254
}
9✔
255

256
fn parse_payload_arg(args: &mut BootArgs) -> Payload {
7✔
257
    let mut payload = Payload {
7✔
258
        firmware: args.firmware.take(),
7✔
259
        initramfs: args.initramfs.take(),
7✔
260
        cmdline: args.cmdline.take(),
7✔
261
        ..Default::default()
7✔
262
    };
7✔
263
    payload.executable = args.kernel.take().map(Executable::Linux);
7✔
264
    #[cfg(target_arch = "x86_64")]
265
    if payload.executable.is_none() {
3✔
266
        payload.executable = args.pvh.take().map(Executable::Pvh);
1✔
267
    }
2✔
268
    payload
7✔
269
}
7✔
270

271
fn parse_args(mut args: BootArgs, objects: HashMap<&str, &str>) -> Result<Config, Error> {
3✔
272
    let payload = parse_payload_arg(&mut args);
3✔
273

274
    let mut board_config = BoardConfig::default();
3✔
275
    if let Some(arg) = args.coco {
3!
276
        let param = serde_aco::from_args(&arg, &objects).context(error::ParseArg { arg })?;
×
277
        board_config.coco = Some(param);
×
278
    };
3✔
279
    board_config.mem = parse_mem_arg(args.memory, args.mem_size, &objects)?;
3✔
280
    board_config.cpu = parse_cpu_arg(args.cpu, args.num_cpu, &objects)?;
3✔
281

282
    let mut config = Config {
3✔
283
        board: board_config,
3✔
284
        pvpanic: args.pvpanic,
3✔
285
        payload,
3✔
286
        ..Default::default()
3✔
287
    };
3✔
288

289
    #[cfg(target_arch = "x86_64")]
290
    for arg in args.fw_cfg {
2✔
291
        let param = serde_aco::from_args(&arg, &objects).context(error::ParseArg { arg })?;
2✔
292
        config.fw_cfg.push(param);
2✔
293
    }
294

295
    if args.entropy {
3!
296
        config.entropy = Some(EntropyParam::default());
3✔
297
    }
3✔
298

299
    for arg in args.net {
5✔
300
        let param = parse_net_arg(&arg, &objects).context(error::ParseArg { arg })?;
5✔
301
        config.net.push(param);
5✔
302
    }
303

304
    for arg in args.blk {
6✔
305
        let param = parse_blk_arg(&arg, &objects);
6✔
306
        config.blk.push(param);
6✔
307
    }
6✔
308

309
    for arg in args.fs {
5✔
310
        let param = serde_aco::from_args(&arg, &objects).context(error::ParseArg { arg })?;
5✔
311
        config.fs.push(param);
5✔
312
    }
313

314
    if let Some(arg) = args.vsock {
3!
315
        let param = serde_aco::from_args(&arg, &objects).context(error::ParseArg { arg })?;
3✔
316
        config.vsock = Some(param);
3✔
317
    }
×
318

319
    if let Some(arg) = args.balloon {
3!
320
        let param = serde_aco::from_args(&arg, &objects).context(error::ParseArg { arg })?;
3✔
321
        config.balloon = Some(param);
3✔
322
    }
×
323

324
    #[cfg(target_os = "linux")]
325
    for arg in args.vfio_ioas {
2✔
326
        let param = serde_aco::from_args(&arg, &objects).context(error::ParseArg { arg })?;
2✔
327
        config.vfio_ioas.push(param);
2✔
328
    }
329
    #[cfg(target_os = "linux")]
330
    for arg in args.vfio_cdev {
2✔
331
        let param = serde_aco::from_args(&arg, &objects).context(error::ParseArg { arg })?;
2✔
332
        config.vfio_cdev.push(param);
2✔
333
    }
334
    #[cfg(target_os = "linux")]
335
    for arg in args.vfio_container {
2✔
336
        let param = serde_aco::from_args(&arg, &objects).context(error::ParseArg { arg })?;
2✔
337
        config.vfio_container.push(param);
2✔
338
    }
339
    #[cfg(target_os = "linux")]
340
    for arg in args.vfio_group {
2✔
341
        let param = serde_aco::from_args(&arg, &objects).context(error::ParseArg { arg })?;
2✔
342
        config.vfio_group.push(param);
2✔
343
    }
344

345
    Ok(config)
3✔
346
}
3✔
347

NEW
348
fn create<H: Hypervisor>(hypervisor: &H, config: Config) -> Result<Machine<H>, alioth::vm::Error> {
×
NEW
349
    let vm = Machine::new(hypervisor, config.board)?;
×
350

351
    #[cfg(target_arch = "x86_64")]
NEW
352
    vm.add_com1()?;
×
353
    #[cfg(target_arch = "aarch64")]
NEW
354
    vm.add_pl011()?;
×
355
    #[cfg(target_arch = "aarch64")]
NEW
356
    vm.add_pl031();
×
357

NEW
358
    if config.pvpanic {
×
NEW
359
        vm.add_pvpanic()?;
×
NEW
360
    }
×
361

362
    #[cfg(target_arch = "x86_64")]
NEW
363
    if config.payload.firmware.is_some() || !config.fw_cfg.is_empty() {
×
NEW
364
        vm.add_fw_cfg(config.fw_cfg.into_iter())?;
×
NEW
365
    };
×
366

NEW
367
    if let Some(param) = config.entropy {
×
NEW
368
        vm.add_virtio_dev("virtio-entropy", param)?;
×
NEW
369
    }
×
370

NEW
371
    for (index, param) in config.net.into_iter().enumerate() {
×
NEW
372
        match param {
×
373
            #[cfg(target_os = "linux")]
NEW
374
            NetParam::Tap(tap_param) => vm.add_virtio_dev(format!("virtio-net-{index}"), tap_param),
×
375
            #[cfg(target_os = "linux")]
NEW
376
            NetParam::Vu(sock) => {
×
NEW
377
                let param = VuFrontendParam {
×
NEW
378
                    id: DeviceId::Net,
×
NEW
379
                    socket: sock.socket,
×
NEW
380
                };
×
NEW
381
                vm.add_virtio_dev(format!("vu-net-{index}"), param)
×
382
            }
383
            #[cfg(target_os = "macos")]
NEW
384
            NetParam::Vmnet(p) => vm.add_virtio_dev(format!("virtio-net-{index}"), p),
×
NEW
385
        }?;
×
386
    }
387

NEW
388
    for (index, param) in config.blk.into_iter().enumerate() {
×
NEW
389
        match param {
×
NEW
390
            BlkParam::File(p) => vm.add_virtio_dev(format!("virtio-blk-{index}"), p),
×
391
            #[cfg(target_os = "linux")]
NEW
392
            BlkParam::Vu(s) => {
×
NEW
393
                let p = VuFrontendParam {
×
NEW
394
                    id: DeviceId::Block,
×
NEW
395
                    socket: s.socket,
×
NEW
396
                };
×
NEW
397
                vm.add_virtio_dev(format!("vu-net-{index}"), p)
×
398
            }
NEW
399
        }?;
×
400
    }
401

NEW
402
    for (index, param) in config.fs.into_iter().enumerate() {
×
NEW
403
        match param {
×
NEW
404
            FsParam::Dir(p) => vm.add_virtio_dev(format!("virtio-fs-{index}"), p),
×
405
            #[cfg(target_os = "linux")]
NEW
406
            FsParam::Vu(p) => vm.add_virtio_dev(format!("vu-fs-{index}"), p),
×
NEW
407
        }?;
×
408
    }
409

NEW
410
    if let Some(param) = config.vsock {
×
NEW
411
        match param {
×
412
            #[cfg(target_os = "linux")]
NEW
413
            VsockParam::Vhost(p) => vm.add_virtio_dev("vhost-vsock", p),
×
NEW
414
            VsockParam::Uds(p) => vm.add_virtio_dev("uds-vsock", p),
×
415
            #[cfg(target_os = "linux")]
NEW
416
            VsockParam::Vu(s) => {
×
NEW
417
                let p = VuFrontendParam {
×
NEW
418
                    id: DeviceId::Socket,
×
NEW
419
                    socket: s.socket,
×
NEW
420
                };
×
NEW
421
                vm.add_virtio_dev("vu-vsock", p)
×
422
            }
NEW
423
        }?;
×
NEW
424
    }
×
425

NEW
426
    if let Some(param) = config.balloon {
×
NEW
427
        vm.add_virtio_dev("virtio-balloon", param)?;
×
NEW
428
    }
×
429

430
    #[cfg(target_os = "linux")]
NEW
431
    for param in config.vfio_ioas.into_iter() {
×
NEW
432
        vm.add_vfio_ioas(param)?;
×
433
    }
434
    #[cfg(target_os = "linux")]
NEW
435
    for (index, param) in config.vfio_cdev.into_iter().enumerate() {
×
NEW
436
        vm.add_vfio_cdev(format!("vfio-{index}").into(), param)?;
×
437
    }
438

439
    #[cfg(target_os = "linux")]
NEW
440
    for param in config.vfio_container.into_iter() {
×
NEW
441
        vm.add_vfio_container(param)?;
×
442
    }
443
    #[cfg(target_os = "linux")]
NEW
444
    for (index, param) in config.vfio_group.into_iter().enumerate() {
×
NEW
445
        vm.add_vfio_devs_in_group(&index.to_string(), param)?;
×
446
    }
447

NEW
448
    vm.add_payload(config.payload);
×
449

NEW
450
    Ok(vm)
×
NEW
451
}
×
452

453
pub fn boot(mut args: BootArgs) -> Result<(), Error> {
×
454
    let object_args = mem::take(&mut args.objects);
×
455
    let objects = parse_objects(&object_args)?;
×
456

457
    let hv_config = if let Some(arg) = args.hypervisor.take() {
×
458
        serde_aco::from_args(&arg, &objects).context(error::ParseArg { arg })?
×
459
    } else {
460
        HvConfig::default()
×
461
    };
462
    let hypervisor = match hv_config {
×
463
        #[cfg(target_os = "linux")]
464
        HvConfig::Kvm(kvm_config) => Kvm::new(kvm_config).context(error::Hypervisor)?,
×
465
        #[cfg(target_os = "macos")]
466
        HvConfig::Hvf => Hvf {},
×
467
    };
468

469
    let config = parse_args(args, objects)?;
×
470

NEW
471
    let vm = create(&hypervisor, config).context(error::CreateVm)?;
×
472

473
    vm.boot().context(error::BootVm)?;
×
474
    vm.wait().context(error::WaitVm)?;
×
475
    Ok(())
×
476
}
×
477

478
#[cfg(test)]
479
#[path = "boot_test.rs"]
480
mod tests;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc