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

google / alioth / 17385686062

01 Sep 2025 07:20PM UTC coverage: 18.016% (-0.1%) from 18.149%
17385686062

Pull #281

github

web-flow
Merge f6f978f6a into 6ec9a6d6b
Pull Request #281: Port to Apple Hypervisor framework

0 of 152 new or added lines in 11 files covered. (0.0%)

1323 existing lines in 30 files now uncovered.

1362 of 7560 relevant lines covered (18.02%)

18.79 hits per line

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

0.0
/alioth-cli/src/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
use std::collections::HashMap;
16
#[cfg(target_arch = "x86_64")]
17
use std::ffi::CString;
18
#[cfg(target_arch = "x86_64")]
19
use std::fs::File;
20
use std::path::PathBuf;
21

22
use alioth::board::BoardConfig;
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
use alioth::hv::{self, Coco};
29
#[cfg(target_os = "linux")]
30
use alioth::hv::{Kvm, KvmConfig};
31
use alioth::loader::{ExecType, 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
use alioth::virtio::dev::fs::shared_dir::SharedDirParam;
41
#[cfg(target_os = "linux")]
42
use alioth::virtio::dev::fs::vu::VuFsParam;
43
#[cfg(target_os = "linux")]
44
use alioth::virtio::dev::net::NetTapParam;
45
use alioth::virtio::dev::vsock::UdsVsockParam;
46
#[cfg(target_os = "linux")]
47
use alioth::virtio::dev::vsock::VhostVsockParam;
48
#[cfg(target_os = "linux")]
49
use alioth::virtio::vu::frontend::VuFrontendParam;
50
use alioth::vm::Machine;
51
use clap::Args;
52
use serde::Deserialize;
53
use serde_aco::{Help, help_text};
54
use snafu::{ResultExt, Snafu};
55

56
use crate::objects::{DOC_OBJECTS, parse_objects};
57

58
#[trace_error]
59
#[derive(Snafu, DebugTrace)]
60
#[snafu(module, context(suffix(false)))]
61
pub enum Error {
62
    #[snafu(display("Failed to parse {arg}"))]
63
    ParseArg {
64
        arg: String,
65
        error: serde_aco::Error,
66
    },
67
    #[snafu(display("Failed to parse objects"), context(false))]
68
    ParseObjects { source: crate::objects::Error },
69
    #[cfg(target_os = "linux")]
70
    #[snafu(display("Failed to access system hypervisor"))]
71
    Hypervisor { source: alioth::hv::Error },
72
    #[snafu(display("Failed to create a VM"))]
73
    CreateVm { source: alioth::vm::Error },
74
    #[snafu(display("Failed to create a device"))]
75
    CreateDevice { source: alioth::vm::Error },
76
    #[cfg(target_arch = "x86_64")]
77
    #[snafu(display("Failed to open {path:?}"))]
78
    OpenFile {
79
        path: PathBuf,
80
        error: std::io::Error,
81
    },
82
    #[cfg(target_arch = "x86_64")]
83
    #[snafu(display("Failed to configure the fw-cfg device"))]
84
    FwCfg { error: std::io::Error },
85
    #[cfg(target_arch = "x86_64")]
86
    #[snafu(display("{s} is not a valid CString"))]
87
    CreateCString { s: String },
88
    #[snafu(display("Failed to boot a VM"))]
89
    BootVm { source: alioth::vm::Error },
90
    #[snafu(display("VM did not shutdown peacefully"))]
91
    WaitVm { source: alioth::vm::Error },
92
}
93

94
#[derive(Debug, Deserialize, Clone, Help)]
95
#[cfg_attr(target_os = "macos", derive(Default))]
96
enum Hypervisor {
97
    /// KVM backed by the Linux kernel.
98
    #[cfg(target_os = "linux")]
99
    #[serde(alias = "kvm")]
100
    Kvm(KvmConfig),
101
    /// macOS Hypervisor Framework.
102
    #[cfg(target_os = "macos")]
103
    #[serde(alias = "hvf")]
104
    #[default]
105
    Hvf,
106
}
107

108
#[cfg(target_os = "linux")]
109
impl Default for Hypervisor {
UNCOV
110
    fn default() -> Self {
UNCOV
111
        Hypervisor::Kvm(KvmConfig::default())
112
    }
113
}
114

115
#[derive(Debug, Deserialize, Clone, Help)]
116
enum FsParam {
117
    /// VirtIO FS device backed by a shared directory.
118
    #[serde(alias = "dir")]
119
    Dir(SharedDirParam),
120
    #[cfg(target_os = "linux")]
121
    /// VirtIO FS device backed by a vhost-user process, e.g. virtiofsd.
122
    #[serde(alias = "vu")]
123
    Vu(VuFsParam),
124
}
125

126
#[derive(Debug, Deserialize, Clone, Help)]
127
enum VsockParam {
128
    #[cfg(target_os = "linux")]
129
    /// Vsock device backed by host kernel vhost-vsock module.
130
    #[serde(alias = "vhost")]
131
    Vhost(VhostVsockParam),
132
    /// Vsock device mapped to a Unix domain socket.
133
    #[serde(alias = "uds")]
134
    Uds(UdsVsockParam),
135
}
136

137
#[cfg(target_os = "linux")]
138
#[derive(Deserialize, Help)]
139
struct VuSocket {
140
    socket: PathBuf,
141
}
142

143
#[cfg(target_os = "linux")]
144
#[derive(Deserialize, Help)]
145
enum NetParam {
146
    /// VirtIO net device backed by TUN/TAP, MacVTap, or IPVTap.
147
    #[serde(alias = "tap")]
148
    Tap(NetTapParam),
149
    /// vhost-user net device over a Unix domain socket.
150
    #[serde(alias = "vu")]
151
    Vu(VuSocket),
152
}
153

154
#[derive(Deserialize, Help)]
155
enum BlkParam {
156
    /// VirtIO block device backed a disk image file.
157
    #[serde(alias = "file")]
158
    File(BlkFileParam),
159
    #[cfg(target_os = "linux")]
160
    #[serde(alias = "vu")]
161
    /// vhost-user block device over a Unix domain socket.
162
    Vu(VuSocket),
163
}
164

165
#[derive(Args, Debug, Clone)]
166
#[command(arg_required_else_help = true, alias("run"))]
167
pub struct BootArgs {
168
    #[arg(long, help(
169
        help_text::<Hypervisor>("Specify the Hypervisor to run on.")
170
    ), value_name = "HV")]
171
    hypervisor: Option<String>,
172

173
    /// Path to a Linux kernel image.
174
    #[arg(short, long, value_name = "PATH")]
175
    kernel: Option<PathBuf>,
176

177
    /// Path to an ELF kernel with PVH note.
178
    #[cfg(target_arch = "x86_64")]
179
    #[arg(long, value_name = "PATH")]
180
    pvh: Option<PathBuf>,
181

182
    /// Path to a firmware image.
183
    #[arg(long, short, value_name = "PATH")]
184
    firmware: Option<PathBuf>,
185

186
    /// Command line to pass to the kernel, e.g. `console=ttyS0`.
187
    #[arg(short, long, value_name = "ARGS")]
188
    cmd_line: Option<String>,
189

190
    /// Path to an initramfs image.
191
    #[arg(short, long, value_name = "PATH")]
192
    initramfs: Option<PathBuf>,
193

194
    /// Number of VCPUs assigned to the guest.
195
    #[arg(long, default_value_t = 1)]
196
    num_cpu: u32,
197

198
    /// DEPRECATED: Use --memory instead.
199
    #[arg(long, default_value = "1G")]
200
    mem_size: String,
201

202
    #[arg(short, long, help(
203
        help_text::<MemConfig>("Specify the memory of the guest.")
204
    ))]
205
    memory: Option<String>,
206

207
    /// Add a pvpanic device.
208
    #[arg(long)]
209
    pvpanic: bool,
210

211
    #[cfg(target_arch = "x86_64")]
212
    #[arg(long = "fw-cfg", help(
213
        help_text::<FwCfgItemParam>("Add an extra item to the fw_cfg device.")
214
    ), value_name = "ITEM")]
215
    fw_cfgs: Vec<String>,
216

217
    /// Add a VirtIO entropy device.
218
    #[arg(long)]
219
    entropy: bool,
220

221
    #[cfg(target_os = "linux")]
222
    #[arg(long, help(
223
        help_text::<NetParam>("Add a VirtIO net device.")
224
    ))]
225
    net: Vec<String>,
226

227
    #[arg(long, help(
228
        help_text::<BlkParam>("Add a VirtIO block device.")
229
    ))]
230
    blk: Vec<String>,
231

232
    #[arg(long, help(
233
        help_text::<Coco>("Enable confidential compute supported by host platform.")
234
    ))]
235
    coco: Option<String>,
236

237
    #[arg(long, help(
238
        help_text::<FsParam>("Add a VirtIO filesystem device.")
239
    ))]
240
    fs: Vec<String>,
241

242
    #[arg(long, help(
243
        help_text::<VsockParam>("Add a VirtIO vsock device.")
244
    ))]
245
    vsock: Option<String>,
246

247
    #[cfg(target_os = "linux")]
248
    #[arg(long, help(help_text::<CdevParam>(
249
        "Assign a host PCI device to the guest using IOMMUFD API."
250
    ) ))]
251
    vfio_cdev: Vec<String>,
252

253
    #[cfg(target_os = "linux")]
254
    #[arg(long, help(help_text::<IoasParam>("Create a new IO address space.")))]
255
    vfio_ioas: Vec<String>,
256

257
    #[cfg(target_os = "linux")]
258
    #[arg(long, help(help_text::<GroupParam>(
259
        "Assign a host PCI device to the guest using legacy VFIO API."
260
    )))]
261
    vfio_group: Vec<String>,
262

263
    #[cfg(target_os = "linux")]
264
    #[arg(long, help(help_text::<ContainerParam>("Add a new VFIO container.")))]
265
    vfio_container: Vec<String>,
266

267
    #[arg(long)]
268
    #[arg(long, help(help_text::<BalloonParam>("Add a VirtIO balloon device.")))]
269
    balloon: Option<String>,
270

271
    #[arg(short, long("object"), help = DOC_OBJECTS, value_name = "OBJECT")]
272
    objects: Vec<String>,
273
}
274

275
#[cfg(target_os = "linux")]
UNCOV
276
fn add_net<H>(
277
    vm: &Machine<H>,
278
    args: Vec<String>,
279
    objects: &HashMap<&str, &str>,
280
) -> Result<(), Error>
281
where
282
    H: hv::Hypervisor + 'static,
283
{
284
    for (index, net_opt) in args.into_iter().enumerate() {
×
285
        let net_param: NetParam = match serde_aco::from_args(&net_opt, objects) {
×
286
            Ok(p) => p,
×
287
            Err(_) => {
×
288
                let tap_param = serde_aco::from_args::<NetTapParam>(&net_opt, objects)
×
289
                    .context(error::ParseArg { arg: net_opt })?;
×
290
                NetParam::Tap(tap_param)
×
291
            }
292
        };
293
        match net_param {
×
294
            NetParam::Tap(tap_param) => vm.add_virtio_dev(format!("virtio-net-{index}"), tap_param),
×
295
            #[cfg(target_os = "linux")]
296
            NetParam::Vu(sock) => {
×
297
                let param = VuFrontendParam {
298
                    id: DeviceId::Net,
299
                    socket: sock.socket,
×
300
                };
301
                vm.add_virtio_dev(format!("vu-net-{index}"), param)
×
302
            }
303
        }
304
        .context(error::CreateDevice)?;
×
305
    }
306
    Ok(())
×
307
}
308

309
fn add_blk<H>(
×
310
    vm: &Machine<H>,
311
    args: Vec<String>,
312
    objects: &HashMap<&str, &str>,
313
) -> Result<(), Error>
314
where
315
    H: hv::Hypervisor + 'static,
316
{
317
    for (index, opt) in args.into_iter().enumerate() {
×
318
        let param: BlkParam = match serde_aco::from_args(&opt, objects) {
×
319
            Ok(param) => param,
×
320
            Err(_) => match serde_aco::from_args(&opt, objects) {
×
321
                Ok(param) => BlkParam::File(param),
×
322
                Err(_) => {
×
323
                    eprintln!("Please update the cmd line to --blk file,path={opt}");
×
324
                    BlkParam::File(BlkFileParam {
×
325
                        path: opt.into(),
×
326
                        ..Default::default()
×
327
                    })
328
                }
329
            },
330
        };
331
        match param {
×
332
            BlkParam::File(p) => vm.add_virtio_dev(format!("virtio-blk-{index}"), p),
×
333
            #[cfg(target_os = "linux")]
334
            BlkParam::Vu(s) => {
×
335
                let p = VuFrontendParam {
336
                    id: DeviceId::Block,
337
                    socket: s.socket,
×
338
                };
339
                vm.add_virtio_dev(format!("vu-net-{index}"), p)
×
340
            }
341
        }
342
        .context(error::CreateDevice)?;
×
343
    }
344
    Ok(())
×
345
}
346

347
pub fn boot(args: BootArgs) -> Result<(), Error> {
×
348
    let objects = parse_objects(&args.objects)?;
×
349
    let hv_config = if let Some(hv_cfg_opt) = args.hypervisor {
×
350
        serde_aco::from_args(&hv_cfg_opt, &objects).context(error::ParseArg { arg: hv_cfg_opt })?
×
351
    } else {
352
        Hypervisor::default()
×
353
    };
354
    let hypervisor = match hv_config {
355
        #[cfg(target_os = "linux")]
UNCOV
356
        Hypervisor::Kvm(kvm_config) => Kvm::new(kvm_config).context(error::Hypervisor)?,
357
        #[cfg(target_os = "macos")]
358
        Hypervisor::Hvf => Hvf {},
359
    };
360
    let coco = match args.coco {
×
361
        None => None,
×
362
        Some(c) => Some(serde_aco::from_args(&c, &objects).context(error::ParseArg { arg: c })?),
×
363
    };
364
    let mem_config = if let Some(s) = args.memory {
×
365
        serde_aco::from_args(&s, &objects).context(error::ParseArg { arg: s })?
×
366
    } else {
367
        #[cfg(target_os = "linux")]
UNCOV
368
        eprintln!(
UNCOV
369
            "Please update the cmd line to --memory size={},backend=memfd",
370
            args.mem_size
371
        );
372
        let size = serde_aco::from_args(&args.mem_size, &objects)
×
373
            .context(error::ParseArg { arg: args.mem_size })?;
×
374
        MemConfig {
375
            size,
376
            #[cfg(target_os = "linux")]
377
            backend: MemBackend::Memfd,
378
            #[cfg(not(target_os = "linux"))]
379
            backend: MemBackend::Anonymous,
380
            ..Default::default()
381
        }
382
    };
383
    let board_config = BoardConfig {
384
        mem: mem_config,
385
        num_cpu: args.num_cpu,
×
386
        coco,
387
    };
388
    let vm = Machine::new(hypervisor, board_config).context(error::CreateVm)?;
×
389
    #[cfg(target_arch = "x86_64")]
390
    vm.add_com1().context(error::CreateDevice)?;
391
    #[cfg(target_arch = "aarch64")]
392
    vm.add_pl011().context(error::CreateDevice)?;
393

394
    if args.pvpanic {
×
395
        vm.add_pvpanic().context(error::CreateDevice)?;
×
396
    }
397

398
    #[cfg(target_arch = "x86_64")]
399
    if args.firmware.is_some() || !args.fw_cfgs.is_empty() {
400
        let params = args
401
            .fw_cfgs
402
            .into_iter()
403
            .map(|s| serde_aco::from_args(&s, &objects).context(error::ParseArg { arg: s }))
404
            .collect::<Result<Vec<_>, _>>()?;
405
        let fw_cfg = vm
406
            .add_fw_cfg(params.into_iter())
407
            .context(error::CreateDevice)?;
408
        let mut dev = fw_cfg.lock();
409

410
        if let Some(kernel) = &args.kernel {
411
            dev.add_kernel_data(File::open(kernel).context(error::OpenFile { path: kernel })?)
412
                .context(error::FwCfg)?
413
        }
414
        if let Some(initramfs) = &args.initramfs {
415
            dev.add_initramfs_data(
416
                File::open(initramfs).context(error::OpenFile { path: initramfs })?,
417
            )
418
            .context(error::FwCfg)?;
419
        }
420
        if let Some(cmdline) = &args.cmd_line {
421
            let Ok(cmdline_c) = CString::new(cmdline.as_str()) else {
422
                return error::CreateCString {
423
                    s: cmdline.to_owned(),
424
                }
425
                .fail();
426
            };
427
            dev.add_kernel_cmdline(cmdline_c);
428
        }
429
    };
430

431
    if args.entropy {
×
432
        vm.add_virtio_dev("virtio-entropy", EntropyParam::default())
×
433
            .context(error::CreateDevice)?;
×
434
    }
435
    #[cfg(target_os = "linux")]
UNCOV
436
    add_net(&vm, args.net, &objects)?;
437
    add_blk(&vm, args.blk, &objects)?;
×
438
    for (index, fs) in args.fs.into_iter().enumerate() {
×
439
        let param: FsParam =
×
440
            serde_aco::from_args(&fs, &objects).context(error::ParseArg { arg: fs })?;
441
        match param {
×
442
            FsParam::Dir(p) => vm.add_virtio_dev(format!("virtio-fs-{index}"), p),
×
443
            #[cfg(target_os = "linux")]
UNCOV
444
            FsParam::Vu(p) => vm.add_virtio_dev(format!("vu-fs-{index}"), p),
445
        }
446
        .context(error::CreateDevice)?;
×
447
    }
448
    if let Some(vsock) = args.vsock {
×
449
        let param =
×
450
            serde_aco::from_args(&vsock, &objects).context(error::ParseArg { arg: vsock })?;
451
        match param {
×
452
            #[cfg(target_os = "linux")]
UNCOV
453
            VsockParam::Vhost(p) => vm
UNCOV
454
                .add_virtio_dev("vhost-vsock", p)
UNCOV
455
                .context(error::CreateDevice)?,
456
            VsockParam::Uds(p) => vm
×
457
                .add_virtio_dev("uds-vsock", p)
×
458
                .context(error::CreateDevice)?,
×
459
        };
460
    }
461
    if let Some(balloon) = args.balloon {
×
462
        let param: BalloonParam =
×
463
            serde_aco::from_args(&balloon, &objects).context(error::ParseArg { arg: balloon })?;
464
        vm.add_virtio_dev("virtio-balloon", param)
×
465
            .context(error::CreateDevice)?;
×
466
    }
467

468
    #[cfg(target_os = "linux")]
UNCOV
469
    for ioas in args.vfio_ioas.into_iter() {
UNCOV
470
        let param: IoasParam =
UNCOV
471
            serde_aco::from_args(&ioas, &objects).context(error::ParseArg { arg: ioas })?;
UNCOV
472
        vm.add_vfio_ioas(param).context(error::CreateDevice)?;
473
    }
474
    #[cfg(target_os = "linux")]
UNCOV
475
    for (index, vfio) in args.vfio_cdev.into_iter().enumerate() {
UNCOV
476
        let param: CdevParam =
UNCOV
477
            serde_aco::from_args(&vfio, &objects).context(error::ParseArg { arg: vfio })?;
UNCOV
478
        vm.add_vfio_cdev(format!("vfio-{index}").into(), param)
UNCOV
479
            .context(error::CreateDevice)?;
480
    }
481

482
    #[cfg(target_os = "linux")]
UNCOV
483
    for container in args.vfio_container.into_iter() {
UNCOV
484
        let param: ContainerParam = serde_aco::from_args(&container, &objects)
UNCOV
485
            .context(error::ParseArg { arg: container })?;
UNCOV
486
        vm.add_vfio_container(param).context(error::CreateDevice)?;
487
    }
488
    #[cfg(target_os = "linux")]
UNCOV
489
    for (index, group) in args.vfio_group.into_iter().enumerate() {
UNCOV
490
        let param: GroupParam =
UNCOV
491
            serde_aco::from_args(&group, &objects).context(error::ParseArg { arg: group })?;
UNCOV
492
        vm.add_vfio_devs_in_group(&index.to_string(), param)
UNCOV
493
            .context(error::CreateDevice)?;
494
    }
495

496
    let payload = if let Some(fw) = args.firmware {
×
497
        Some(Payload {
×
498
            executable: fw,
499
            exec_type: ExecType::Firmware,
×
500
            initramfs: None,
×
501
            cmd_line: None,
×
502
        })
503
    } else if let Some(kernel) = args.kernel {
×
504
        Some(Payload {
×
505
            exec_type: ExecType::Linux,
×
506
            executable: kernel,
507
            initramfs: args.initramfs,
×
508
            cmd_line: args.cmd_line,
×
509
        })
510
    } else {
511
        #[cfg(target_arch = "x86_64")]
512
        if let Some(pvh_kernel) = args.pvh {
513
            Some(Payload {
514
                executable: pvh_kernel,
515
                exec_type: ExecType::Pvh,
516
                initramfs: args.initramfs,
517
                cmd_line: args.cmd_line,
518
            })
519
        } else {
520
            None
521
        }
522
        #[cfg(not(target_arch = "x86_64"))]
523
        None
524
    };
525
    if let Some(payload) = payload {
×
526
        vm.add_payload(payload);
×
527
    }
528

529
    vm.boot().context(error::BootVm)?;
×
530
    vm.wait().context(error::WaitVm)?;
×
531
    Ok(())
×
532
}
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

© 2025 Coveralls, Inc