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

google / alioth / 30699250729

01 Aug 2026 12:13PM UTC coverage: 25.403% (-2.4%) from 27.814%
30699250729

Pull #475

github

web-flow
Merge cf002a11d into e32fad138
Pull Request #475: ci: Bump the actions group with 5 updates

481 of 1568 branches covered (30.68%)

Branch coverage included in aggregate %.

3943 of 15847 relevant lines covered (24.88%)

37.26 hits per line

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

42.15
/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::mem;
19
use std::path::{Path, PathBuf};
20

21
use alioth::board::{BoardSpec, CpuSpec};
22
use alioth::device::console::ConsoleSpec;
23
#[cfg(target_arch = "x86_64")]
24
use alioth::device::fw_cfg::FwCfgItemSpec;
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::{CocoSpec, HvSpec, Hypervisor};
31
use alioth::loader::{Executable, PayloadSpec};
32
use alioth::mem::{MemBackend, MemSpec};
33
#[cfg(target_os = "linux")]
34
use alioth::vfio::{VfioCdevSpec, VfioContainerSpec, VfioGroupSpec, VfioIoasSpec};
35
#[cfg(target_os = "linux")]
36
use alioth::virtio::DeviceId;
37
use alioth::virtio::dev::balloon::BalloonSpec;
38
use alioth::virtio::dev::blk::BlkFileSpec;
39
use alioth::virtio::dev::entropy::EntropySpec;
40
#[cfg(target_os = "linux")]
41
use alioth::virtio::vu::frontend::VuFrontendSpec;
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::{BlkSpec, FsSpec, NetSpec, VmSpec, VsockSpec};
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::<HvSpec>("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<Box<str>>,
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::<CpuSpec>("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::<MemSpec>("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::<FwCfgItemSpec>("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::<NetSpec>("Add a VirtIO net device.")
137
    ))]
138
    net: Vec<String>,
139

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

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

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

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

160
    #[arg(long, help(
161
        help_text::<ConsoleSpec>("Configure guest console.")
162
    ))]
163
    console: Option<Box<str>>,
164

165
    #[cfg(target_os = "linux")]
166
    #[arg(long, help(help_text::<VfioCdevSpec>(
167
        "Assign a host PCI device to the guest using IOMMUFD API."
168
    ) ))]
169
    vfio_cdev: Vec<String>,
170

171
    #[cfg(target_os = "linux")]
172
    #[arg(long, help(help_text::<VfioIoasSpec>("Create a new IO address space.")))]
173
    vfio_ioas: Vec<String>,
174

175
    #[cfg(target_os = "linux")]
176
    #[arg(long, help(help_text::<VfioGroupSpec>(
177
        "Assign a host PCI device to the guest using legacy VFIO API."
178
    )))]
179
    vfio_group: Vec<String>,
180

181
    #[cfg(target_os = "linux")]
182
    #[arg(long, help(help_text::<VfioContainerSpec>("Add a new VFIO container.")))]
183
    vfio_container: Vec<String>,
184

185
    #[arg(long)]
186
    #[arg(long, help(help_text::<BalloonSpec>("Add a VirtIO balloon device.")))]
187
    balloon: Option<String>,
×
188

189
    #[arg(short, long("object"), help = DOC_OBJECTS, value_name = "OBJECT")]
×
190
    objects: Vec<String>,
×
191
}
192

×
193
fn parse_net_arg(arg: &str, objects: &HashMap<&str, &str>) -> serde_aco::Result<NetSpec> {
14✔
194
    #[cfg(target_os = "linux")]
195
    if let Ok(param) = serde_aco::from_args(arg, objects) {
12✔
196
        Ok(param)
4✔
197
    } else {
×
198
        let param = serde_aco::from_args(arg, objects)?;
8✔
199
        Ok(NetSpec::Tap(param))
8✔
200
    }
×
201

×
202
    #[cfg(target_os = "macos")]
×
203
    serde_aco::from_args(arg, objects)
2!
204
}
14✔
205

206
fn parse_blk_arg(arg: &str, objects: &HashMap<&str, &str>) -> BlkSpec {
29✔
207
    if let Ok(param) = serde_aco::from_args(arg, objects) {
29✔
208
        param
19✔
209
    } else if let Ok(param) = serde_aco::from_args(arg, objects) {
10✔
210
        BlkSpec::File(param)
5✔
211
    } else {
×
212
        eprintln!("Please update the cmd line to --blk file,path={arg}");
5✔
213
        BlkSpec::File(BlkFileSpec {
5✔
214
            path: PathBuf::from(arg).into(),
5✔
215
            readonly: false,
5✔
216
            api: WorkerApi::Mio,
×
217
        })
×
218
    }
×
219
}
29✔
220

×
221
fn parse_mem_arg(
14✔
222
    arg: Option<String>,
14✔
223
    mem_size: String,
14✔
224
    objects: &HashMap<&str, &str>,
14✔
225
) -> Result<MemSpec, Error> {
14✔
226
    let spec = if let Some(arg) = arg {
14✔
227
        serde_aco::from_args(&arg, objects).context(error::ParseArg { arg })?
9✔
228
    } else {
229
        #[cfg(target_os = "linux")]
×
230
        eprintln!("Please update the cmd line to --memory size={mem_size},backend=memfd");
4✔
231
        MemSpec {
×
232
            size: serde_aco::from_args(&mem_size, objects)
5✔
233
                .context(error::ParseArg { arg: mem_size })?,
5✔
234
            #[cfg(target_os = "linux")]
235
            backend: MemBackend::Memfd,
4✔
236
            #[cfg(not(target_os = "linux"))]
×
237
            backend: MemBackend::Anonymous,
1✔
238
            ..Default::default()
5✔
239
        }
×
240
    };
×
241
    Ok(spec)
14✔
242
}
14✔
243

×
244
fn parse_cpu_arg(
15✔
245
    arg: Option<Box<str>>,
15✔
246
    num_cpu: u16,
15✔
247
    objects: &HashMap<&str, &str>,
15✔
248
) -> Result<CpuSpec, Error> {
×
249
    let mut spec = if let Some(arg) = arg {
15✔
250
        serde_aco::from_args(&arg, objects).context(error::ParseArg { arg })?
10✔
251
    } else {
252
        eprintln!("Please update the cmd line to --cpu count={num_cpu}");
5!
253
        CpuSpec {
5✔
254
            count: num_cpu,
5✔
255
            ..Default::default()
5✔
256
        }
5!
257
    };
×
258
    if spec.topology.sockets == 0 {
15✔
259
        spec.topology.sockets = 1;
5✔
260
    }
10✔
261
    let vcpus_per_core = 1 + spec.topology.smt as u16;
15✔
262
    if spec.topology.cores == 0 {
15✔
263
        spec.topology.cores = spec.count / spec.topology.sockets as u16 / vcpus_per_core;
5✔
264
    }
10✔
265
    Ok(spec)
15✔
266
}
15✔
267

×
268
fn parse_payload_arg(args: &mut BootArgs) -> PayloadSpec {
12✔
269
    let mut payload = PayloadSpec {
12✔
270
        firmware: args.firmware.take(),
12✔
271
        initramfs: args.initramfs.take(),
12!
272
        cmdline: args.cmdline.take(),
12✔
273
        ..Default::default()
6✔
274
    };
12✔
275
    payload.executable = args.kernel.take().map(Executable::Linux);
12✔
276
    #[cfg(target_arch = "x86_64")]
277
    if payload.executable.is_none() {
6✔
278
        payload.executable = args.pvh.take().map(Executable::Pvh);
2✔
279
    }
4✔
280
    payload
12✔
281
}
12!
282

×
283
fn parse_args(mut args: BootArgs, objects: HashMap<&str, &str>) -> Result<VmSpec, Error> {
5✔
284
    let payload = parse_payload_arg(&mut args);
5✔
285

×
286
    let mut board_spec = BoardSpec::default();
5✔
287
    if let Some(arg) = args.coco {
5!
288
        let param = serde_aco::from_args(&arg, &objects).context(error::ParseArg { arg })?;
×
289
        board_spec.coco = Some(param);
×
290
    };
5✔
291
    board_spec.mem = parse_mem_arg(args.memory, args.mem_size, &objects)?;
5✔
292
    board_spec.cpu = parse_cpu_arg(args.cpu, args.num_cpu, &objects)?;
5✔
293

×
294
    let mut spec = VmSpec {
5✔
295
        board: board_spec,
5✔
296
        pvpanic: args.pvpanic,
5✔
297
        payload,
5✔
298
        ..Default::default()
5✔
299
    };
5✔
300

301
    #[cfg(target_arch = "x86_64")]
×
302
    for arg in args.fw_cfg {
4✔
303
        let param = serde_aco::from_args(&arg, &objects).context(error::ParseArg { arg })?;
4✔
304
        spec.fw_cfg.push(param);
4✔
305
    }
×
306

×
307
    if args.entropy {
5!
308
        spec.entropy = Some(EntropySpec::default());
5✔
309
    }
5✔
310

×
311
    for arg in args.net {
9✔
312
        let param = parse_net_arg(&arg, &objects).context(error::ParseArg { arg })?;
9✔
313
        spec.net.push(param);
9✔
314
    }
315

×
316
    for arg in args.blk {
10✔
317
        let param = parse_blk_arg(&arg, &objects);
10✔
318
        spec.blk.push(param);
10✔
319
    }
10✔
320

×
321
    for arg in args.fs {
9✔
322
        let param = serde_aco::from_args(&arg, &objects).context(error::ParseArg { arg })?;
9✔
323
        spec.fs.push(param);
9✔
324
    }
325

×
326
    if let Some(arg) = args.vsock {
5!
327
        let param = serde_aco::from_args(&arg, &objects).context(error::ParseArg { arg })?;
5✔
328
        spec.vsock = Some(param);
5✔
329
    }
×
330

331
    if let Some(arg) = args.console {
5!
332
        let param = serde_aco::from_args(&arg, &objects).context(error::ParseArg { arg })?;
×
333
        spec.console = param;
×
334
    }
5✔
335

336
    if let Some(arg) = args.balloon {
5!
337
        let param = serde_aco::from_args(&arg, &objects).context(error::ParseArg { arg })?;
5✔
338
        spec.balloon = Some(param);
1✔
339
    }
×
340

341
    #[cfg(target_os = "linux")]
×
342
    for arg in args.vfio_ioas {
4✔
343
        let param = serde_aco::from_args(&arg, &objects).context(error::ParseArg { arg })?;
4✔
344
        spec.vfio_ioas.push(param);
4✔
345
    }
346
    #[cfg(target_os = "linux")]
×
347
    for arg in args.vfio_cdev {
4✔
348
        let param = serde_aco::from_args(&arg, &objects).context(error::ParseArg { arg })?;
4✔
349
        spec.vfio_cdev.push(param);
4✔
350
    }
351
    #[cfg(target_os = "linux")]
×
352
    for arg in args.vfio_container {
4✔
353
        let param = serde_aco::from_args(&arg, &objects).context(error::ParseArg { arg })?;
4✔
354
        spec.vfio_container.push(param);
4✔
355
    }
×
356
    #[cfg(target_os = "linux")]
357
    for arg in args.vfio_group {
4✔
358
        let param = serde_aco::from_args(&arg, &objects).context(error::ParseArg { arg })?;
4✔
359
        spec.vfio_group.push(param);
4✔
360
    }
×
361

362
    Ok(spec)
2✔
363
}
5✔
364

×
365
fn create<H: Hypervisor>(hypervisor: &H, spec: VmSpec) -> Result<Machine<H>, alioth::vm::Error> {
×
366
    let vm = Machine::new(hypervisor, spec.board)?;
×
367

368
    #[cfg(target_arch = "x86_64")]
369
    vm.add_com1(&spec.console)?;
×
370
    #[cfg(target_arch = "aarch64")]
×
371
    vm.add_pl011(&spec.console)?;
×
372
    #[cfg(target_arch = "aarch64")]
×
373
    vm.add_pl031();
×
374

375
    if spec.pvpanic {
×
376
        vm.add_pvpanic()?;
×
377
    }
×
378

379
    #[cfg(target_arch = "x86_64")]
×
380
    if spec.payload.firmware.is_some() {
×
381
        vm.add_cmos()?;
×
382
        vm.add_fw_dbg()?;
×
383
    }
×
384

×
385
    #[cfg(target_arch = "x86_64")]
386
    if spec.payload.firmware.is_some() || !spec.fw_cfg.is_empty() {
×
387
        vm.add_fw_cfg(spec.fw_cfg.into_iter())?;
×
388
    };
×
389

×
390
    if let Some(entropy_spec) = spec.entropy {
×
391
        vm.add_virtio_dev("virtio-entropy", entropy_spec)?;
×
392
    }
×
393

×
394
    for (index, net_spec) in spec.net.into_iter().enumerate() {
×
395
        match net_spec {
×
396
            #[cfg(target_os = "linux")]
×
397
            NetSpec::Tap(tap_spec) => vm.add_virtio_dev(format!("virtio-net-{index}"), tap_spec),
×
398
            #[cfg(target_os = "linux")]
399
            NetSpec::Vu(sock) => {
×
400
                let vu_spec = VuFrontendSpec {
×
401
                    id: DeviceId::NET,
×
402
                    socket: sock.socket,
×
403
                };
×
404
                vm.add_virtio_dev(format!("vu-net-{index}"), vu_spec)
×
405
            }
×
406
            #[cfg(target_os = "macos")]
×
407
            NetSpec::Vmnet(vmnet_spec) => {
×
408
                vm.add_virtio_dev(format!("virtio-net-{index}"), vmnet_spec)
×
409
            }
×
410
        }?;
×
411
    }
×
412

413
    for (index, blk_spec) in spec.blk.into_iter().enumerate() {
×
414
        match blk_spec {
×
415
            BlkSpec::File(file_spec) => vm.add_virtio_dev(format!("virtio-blk-{index}"), file_spec),
×
416
            #[cfg(target_os = "linux")]
×
417
            BlkSpec::Vu(s) => {
×
418
                let vu_spec = VuFrontendSpec {
×
419
                    id: DeviceId::BLOCK,
×
420
                    socket: s.socket,
×
421
                };
×
422
                vm.add_virtio_dev(format!("vu-net-{index}"), vu_spec)
×
423
            }
×
424
        }?;
×
425
    }
×
426

×
427
    for (index, fs_spec) in spec.fs.into_iter().enumerate() {
×
428
        match fs_spec {
×
429
            FsSpec::Dir(dir_spec) => vm.add_virtio_dev(format!("virtio-fs-{index}"), dir_spec),
×
430
            #[cfg(target_os = "linux")]
×
431
            FsSpec::Vu(vu_fs_spec) => vm.add_virtio_dev(format!("vu-fs-{index}"), vu_fs_spec),
×
432
        }?;
×
433
    }
×
434

435
    if let Some(vsock_spec) = spec.vsock {
×
436
        match vsock_spec {
×
437
            #[cfg(target_os = "linux")]
438
            VsockSpec::Vhost(vhost_spec) => vm.add_virtio_dev("vhost-vsock", vhost_spec),
×
439
            VsockSpec::Uds(uds_spec) => vm.add_virtio_dev("uds-vsock", uds_spec),
×
440
            #[cfg(target_os = "linux")]
×
441
            VsockSpec::Vu(s) => {
×
442
                let vu_spec = VuFrontendSpec {
×
443
                    id: DeviceId::SOCKET,
×
444
                    socket: s.socket,
×
445
                };
×
446
                vm.add_virtio_dev("vu-vsock", vu_spec)
×
447
            }
×
448
        }?;
×
449
    }
×
450

451
    if let Some(balloon_spec) = spec.balloon {
×
452
        vm.add_virtio_dev("virtio-balloon", balloon_spec)?;
×
453
    }
×
454

455
    #[cfg(target_os = "linux")]
456
    for ioas_spec in spec.vfio_ioas.into_iter() {
×
457
        vm.add_vfio_ioas(ioas_spec)?;
×
458
    }
459
    #[cfg(target_os = "linux")]
460
    for (index, cdev_spec) in spec.vfio_cdev.into_iter().enumerate() {
×
461
        vm.add_vfio_cdev(format!("vfio-{index}").into(), cdev_spec)?;
×
462
    }
×
463

×
464
    #[cfg(target_os = "linux")]
465
    for container_spec in spec.vfio_container.into_iter() {
×
466
        vm.add_vfio_container(container_spec)?;
×
467
    }
×
468
    #[cfg(target_os = "linux")]
469
    for (index, group_spec) in spec.vfio_group.into_iter().enumerate() {
×
470
        vm.add_vfio_devs_in_group(&index.to_string(), group_spec)?;
×
471
    }
472

×
473
    vm.add_payload(spec.payload);
×
474

×
475
    Ok(vm)
×
476
}
×
477

478
pub fn boot(mut args: BootArgs) -> Result<(), Error> {
×
479
    let object_args = mem::take(&mut args.objects);
×
480
    let objects = parse_objects(&object_args)?;
×
481

×
482
    let hv_spec = if let Some(arg) = args.hypervisor.take() {
×
483
        serde_aco::from_args(&arg, &objects).context(error::ParseArg { arg })?
×
484
    } else {
485
        HvSpec::default()
×
486
    };
×
487
    let hypervisor = match hv_spec {
×
488
        #[cfg(target_os = "linux")]
×
489
        HvSpec::Kvm(kvm_spec) => Kvm::new(kvm_spec).context(error::Hypervisor)?,
×
490
        #[cfg(target_os = "macos")]
491
        HvSpec::Hvf => Hvf {},
×
492
    };
493

494
    let spec = parse_args(args, objects)?;
×
495

496
    let vm = create(&hypervisor, spec).context(error::CreateVm)?;
×
497

498
    vm.boot().context(error::BootVm)?;
×
499
    vm.wait().context(error::WaitVm)?;
×
500
    Ok(())
×
501
}
×
502

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

© 2026 Coveralls, Inc