openvmm_entry/
lib.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! This module implements the interactive control process and the entry point
5//! for the worker process.
6
7#![expect(missing_docs)]
8#![cfg_attr(not(test), forbid(unsafe_code))]
9
10mod cli_args;
11mod crash_dump;
12mod kvp;
13mod meshworker;
14mod serial_io;
15mod storage_builder;
16mod tracing_init;
17mod ttrpc;
18
19// `pub` so that the missing_docs warning fires for options without
20// documentation.
21pub use cli_args::Options;
22use console_relay::ConsoleLaunchOptions;
23
24use crate::cli_args::SecureBootTemplateCli;
25use anyhow::Context;
26use anyhow::bail;
27use chipset_resources::battery::HostBatteryUpdate;
28use clap::CommandFactory;
29use clap::FromArgMatches;
30use clap::Parser;
31use cli_args::DiskCliKind;
32use cli_args::EndpointConfigCli;
33use cli_args::NicConfigCli;
34use cli_args::ProvisionVmgs;
35use cli_args::SerialConfigCli;
36use cli_args::UefiConsoleModeCli;
37use cli_args::VirtioBusCli;
38use cli_args::VmgsCli;
39use crash_dump::spawn_dump_handler;
40use disk_backend_resources::DelayDiskHandle;
41use disk_backend_resources::DiskLayerDescription;
42use disk_backend_resources::layer::DiskLayerHandle;
43use disk_backend_resources::layer::RamDiskLayerHandle;
44use disk_backend_resources::layer::SqliteAutoCacheDiskLayerHandle;
45use disk_backend_resources::layer::SqliteDiskLayerHandle;
46use floppy_resources::FloppyDiskConfig;
47use framebuffer::FRAMEBUFFER_SIZE;
48use framebuffer::FramebufferAccess;
49use futures::AsyncReadExt;
50use futures::AsyncWrite;
51use futures::AsyncWriteExt;
52use futures::FutureExt;
53use futures::StreamExt;
54use futures::executor::block_on;
55use futures::io::AllowStdIo;
56use futures_concurrency::stream::Merge;
57use gdma_resources::GdmaDeviceHandle;
58use gdma_resources::VportDefinition;
59use get_resources::ged::GuestServicingFlags;
60use guid::Guid;
61use hvlite_defs::config::Config;
62use hvlite_defs::config::DEFAULT_MMIO_GAPS_AARCH64;
63use hvlite_defs::config::DEFAULT_MMIO_GAPS_AARCH64_WITH_VTL2;
64use hvlite_defs::config::DEFAULT_MMIO_GAPS_X86;
65use hvlite_defs::config::DEFAULT_MMIO_GAPS_X86_WITH_VTL2;
66use hvlite_defs::config::DEFAULT_PCAT_BOOT_ORDER;
67use hvlite_defs::config::DeviceVtl;
68use hvlite_defs::config::HypervisorConfig;
69use hvlite_defs::config::LateMapVtl0MemoryPolicy;
70use hvlite_defs::config::LoadMode;
71use hvlite_defs::config::MemoryConfig;
72use hvlite_defs::config::ProcessorTopologyConfig;
73use hvlite_defs::config::SerialInformation;
74use hvlite_defs::config::VirtioBus;
75use hvlite_defs::config::VmbusConfig;
76use hvlite_defs::config::VpciDeviceConfig;
77use hvlite_defs::config::Vtl2BaseAddressType;
78use hvlite_defs::config::Vtl2Config;
79use hvlite_defs::rpc::PulseSaveRestoreError;
80use hvlite_defs::rpc::VmRpc;
81use hvlite_defs::worker::VM_WORKER;
82use hvlite_defs::worker::VmWorkerParameters;
83use hvlite_helpers::disk::create_disk_type;
84use hvlite_helpers::disk::open_disk_type;
85use input_core::MultiplexedInputHandle;
86use inspect::InspectMut;
87use inspect::InspectionBuilder;
88use io::Read;
89use mesh::CancelContext;
90use mesh::CellUpdater;
91use mesh::error::RemoteError;
92use mesh::rpc::Rpc;
93use mesh::rpc::RpcError;
94use mesh::rpc::RpcSend;
95use mesh_worker::WorkerEvent;
96use mesh_worker::WorkerHandle;
97use mesh_worker::launch_local_worker;
98use meshworker::VmmMesh;
99use net_backend_resources::mac_address::MacAddress;
100use pal_async::DefaultDriver;
101use pal_async::DefaultPool;
102use pal_async::pipe::PolledPipe;
103use pal_async::socket::PolledSocket;
104use pal_async::task::Spawn;
105use pal_async::task::Task;
106use pal_async::timer::PolledTimer;
107use scsidisk_resources::SimpleScsiDiskHandle;
108use scsidisk_resources::SimpleScsiDvdHandle;
109use serial_16550_resources::ComPort;
110use serial_core::resources::DisconnectedSerialBackendHandle;
111use serial_io::SerialIo;
112use sparse_mmap::alloc_shared_memory;
113use std::cell::RefCell;
114use std::fmt::Write as _;
115use std::future::pending;
116use std::io;
117#[cfg(unix)]
118use std::io::IsTerminal;
119use std::io::Write;
120use std::net::TcpListener;
121use std::path::Path;
122use std::path::PathBuf;
123use std::pin::pin;
124use std::sync::Arc;
125use std::thread;
126use std::time::Duration;
127use std::time::Instant;
128use storvsp_resources::ScsiControllerRequest;
129use storvsp_resources::ScsiDeviceAndPath;
130use storvsp_resources::ScsiPath;
131use tpm_resources::TpmDeviceHandle;
132use tpm_resources::TpmRegisterLayout;
133use tracing_helpers::AnyhowValueExt;
134use ttrpc::TtrpcWorker;
135use uidevices_resources::SynthKeyboardHandle;
136use uidevices_resources::SynthMouseHandle;
137use uidevices_resources::SynthVideoHandle;
138use video_core::SharedFramebufferHandle;
139use virtio_resources::VirtioPciDeviceHandle;
140use vm_manifest_builder::BaseChipsetType;
141use vm_manifest_builder::MachineArch;
142use vm_manifest_builder::VmChipsetResult;
143use vm_manifest_builder::VmManifestBuilder;
144use vm_resource::IntoResource;
145use vm_resource::Resource;
146use vm_resource::kind::DiskHandleKind;
147use vm_resource::kind::DiskLayerHandleKind;
148use vm_resource::kind::NetEndpointHandleKind;
149use vm_resource::kind::VirtioDeviceHandle;
150use vm_resource::kind::VmbusDeviceHandleKind;
151use vmbus_serial_resources::VmbusSerialDeviceHandle;
152use vmbus_serial_resources::VmbusSerialPort;
153use vmcore::non_volatile_store::resources::EphemeralNonVolatileStoreHandle;
154use vmgs_resources::VmgsFileHandle;
155use vmgs_resources::VmgsResource;
156use vmotherboard::ChipsetDeviceHandle;
157use vnc_worker_defs::VncParameters;
158
159pub fn hvlite_main() {
160    // Save the current state of the terminal so we can restore it back to
161    // normal before exiting.
162    #[cfg(unix)]
163    let orig_termios = io::stderr().is_terminal().then(term::get_termios);
164
165    let exit_code = match do_main() {
166        Ok(_) => 0,
167        Err(err) => {
168            eprintln!("fatal error: {:?}", err);
169            1
170        }
171    };
172
173    // Restore the terminal to its initial state.
174    #[cfg(unix)]
175    if let Some(orig_termios) = orig_termios {
176        term::set_termios(orig_termios);
177    }
178
179    // Terminate the process immediately without graceful shutdown of DLLs or
180    // C++ destructors or anything like that. This is all unnecessary and saves
181    // time on Windows.
182    //
183    // Do flush stdout, though, since there may be buffered data.
184    let _ = io::stdout().flush();
185    pal::process::terminate(exit_code);
186}
187
188#[derive(Default)]
189struct VmResources {
190    console_in: Option<Box<dyn AsyncWrite + Send + Unpin>>,
191    framebuffer_access: Option<FramebufferAccess>,
192    shutdown_ic: Option<mesh::Sender<hyperv_ic_resources::shutdown::ShutdownRpc>>,
193    kvp_ic: Option<mesh::Sender<hyperv_ic_resources::kvp::KvpConnectRpc>>,
194    scsi_rpc: Option<mesh::Sender<ScsiControllerRequest>>,
195    ged_rpc: Option<mesh::Sender<get_resources::ged::GuestEmulationRequest>>,
196    #[cfg(windows)]
197    switch_ports: Vec<vmswitch::kernel::SwitchPort>,
198}
199
200struct ConsoleState<'a> {
201    device: &'a str,
202    input: Box<dyn AsyncWrite + Unpin + Send>,
203}
204
205fn vm_config_from_command_line(
206    spawner: impl Spawn,
207    opt: &Options,
208) -> anyhow::Result<(Config, VmResources)> {
209    let (_, serial_driver) = DefaultPool::spawn_on_thread("serial");
210    // Ensure the serial driver stays alive with no tasks.
211    serial_driver.spawn("leak", pending::<()>()).detach();
212
213    let openhcl_vtl = if opt.vtl2 {
214        DeviceVtl::Vtl2
215    } else {
216        DeviceVtl::Vtl0
217    };
218
219    let console_state: RefCell<Option<ConsoleState<'_>>> = RefCell::new(None);
220    let setup_serial = |name: &str, cli_cfg, device| -> anyhow::Result<_> {
221        Ok(match cli_cfg {
222            SerialConfigCli::Console => {
223                if let Some(console_state) = console_state.borrow().as_ref() {
224                    bail!("console already set by {}", console_state.device);
225                }
226                let (config, serial) = serial_io::anonymous_serial_pair(&serial_driver)?;
227                let (serial_read, serial_write) = AsyncReadExt::split(serial);
228                *console_state.borrow_mut() = Some(ConsoleState {
229                    device,
230                    input: Box::new(serial_write),
231                });
232                thread::Builder::new()
233                    .name(name.to_owned())
234                    .spawn(move || {
235                        let _ = block_on(futures::io::copy(
236                            serial_read,
237                            &mut AllowStdIo::new(term::raw_stdout()),
238                        ));
239                    })
240                    .unwrap();
241                Some(config)
242            }
243            SerialConfigCli::Stderr => {
244                let (config, serial) = serial_io::anonymous_serial_pair(&serial_driver)?;
245                thread::Builder::new()
246                    .name(name.to_owned())
247                    .spawn(move || {
248                        let _ = block_on(futures::io::copy(
249                            serial,
250                            &mut AllowStdIo::new(term::raw_stderr()),
251                        ));
252                    })
253                    .unwrap();
254                Some(config)
255            }
256            SerialConfigCli::File(path) => {
257                let (config, serial) = serial_io::anonymous_serial_pair(&serial_driver)?;
258                let file = fs_err::File::create(path).context("failed to create file")?;
259
260                thread::Builder::new()
261                    .name(name.to_owned())
262                    .spawn(move || {
263                        let _ = block_on(futures::io::copy(serial, &mut AllowStdIo::new(file)));
264                    })
265                    .unwrap();
266                Some(config)
267            }
268            SerialConfigCli::None => None,
269            SerialConfigCli::Pipe(path) => {
270                Some(serial_io::bind_serial(&path).context("failed to bind serial")?)
271            }
272            SerialConfigCli::Tcp(addr) => {
273                Some(serial_io::bind_tcp_serial(&addr).context("failed to bind serial")?)
274            }
275            SerialConfigCli::NewConsole(app, window_title) => {
276                let path = console_relay::random_console_path();
277                let config =
278                    serial_io::bind_serial(&path).context("failed to bind console serial")?;
279                let window_title =
280                    window_title.unwrap_or_else(|| name.to_uppercase() + " [OpenVMM]");
281
282                console_relay::launch_console(
283                    app.or_else(openvmm_terminal_app).as_deref(),
284                    &path,
285                    ConsoleLaunchOptions {
286                        window_title: Some(window_title + " [OpenVMM]"),
287                    },
288                )
289                .context("failed to launch console")?;
290
291                Some(config)
292            }
293        })
294    };
295
296    // TODO: unify virtio serial handling and remove this.
297    let setup_serial_virtio = |name, cli_cfg, device| -> anyhow::Result<_> {
298        Ok(match cli_cfg {
299            SerialConfigCli::Console => {
300                if console_state.borrow().is_some() {
301                    bail!("console already set");
302                }
303                let mut io = SerialIo::new().context("creating serial IO")?;
304                io.spawn_copy_out(name, term::raw_stdout());
305                *console_state.borrow_mut() = Some(ConsoleState {
306                    device,
307                    input: Box::new(PolledPipe::new(&serial_driver, io.input.unwrap())?),
308                });
309                Some(io.config)
310            }
311            SerialConfigCli::Stderr => {
312                let mut io = SerialIo::new().context("creating serial IO")?;
313                io.spawn_copy_out(name, term::raw_stderr());
314                // Ensure there is no input so that the serial devices don't see
315                // EOF and think the port is disconnected.
316                io.config.input = None;
317                Some(io.config)
318            }
319            SerialConfigCli::File(path) => {
320                let mut io = SerialIo::new().context("creating serial IO")?;
321                let file = fs_err::File::create(path).context("failed to create file")?;
322                io.spawn_copy_out(name, file);
323                // Ensure there is no input so that the serial devices don't see
324                // EOF and think the port is disconnected.
325                io.config.input = None;
326                Some(io.config)
327            }
328            SerialConfigCli::None => None,
329            SerialConfigCli::Pipe(path) => {
330                let mut io = SerialIo::new().context("creating serial IO")?;
331                io.spawn_copy_listener(serial_driver.clone(), name, &path)
332                    .with_context(|| format!("listening on pipe {}", path.display()))?
333                    .detach();
334                Some(io.config)
335            }
336            SerialConfigCli::Tcp(_addr) => anyhow::bail!("TCP virtio serial not supported"),
337            SerialConfigCli::NewConsole(app, window_title) => {
338                let path = console_relay::random_console_path();
339
340                let mut io = SerialIo::new().context("creating serial IO")?;
341                io.spawn_copy_listener(serial_driver.clone(), name, &path)
342                    .with_context(|| format!("listening on pipe {}", path.display()))?
343                    .detach();
344
345                let window_title =
346                    window_title.unwrap_or_else(|| name.to_uppercase() + " [OpenVMM]");
347
348                console_relay::launch_console(
349                    app.or_else(openvmm_terminal_app).as_deref(),
350                    &path,
351                    ConsoleLaunchOptions {
352                        window_title: Some(window_title),
353                    },
354                )
355                .context("failed to launch console")?;
356                Some(io.config)
357            }
358        })
359    };
360
361    let virtio_console = opt.virtio_console || opt.virtio_console_pci;
362    let mut vmbus_devices = Vec::new();
363
364    let serial0_cfg = setup_serial(
365        "com1",
366        opt.com1.clone().unwrap_or({
367            if !virtio_console {
368                SerialConfigCli::Console
369            } else {
370                SerialConfigCli::None
371            }
372        }),
373        if cfg!(guest_arch = "x86_64") {
374            "ttyS0"
375        } else {
376            "ttyAMA0"
377        },
378    )?;
379    let serial1_cfg = setup_serial(
380        "com2",
381        opt.com2.clone().unwrap_or(SerialConfigCli::None),
382        if cfg!(guest_arch = "x86_64") {
383            "ttyS1"
384        } else {
385            "ttyAMA1"
386        },
387    )?;
388    let serial2_cfg = setup_serial(
389        "com3",
390        opt.com3.clone().unwrap_or(SerialConfigCli::None),
391        if cfg!(guest_arch = "x86_64") {
392            "ttyS2"
393        } else {
394            "ttyAMA2"
395        },
396    )?;
397    let serial3_cfg = setup_serial(
398        "com4",
399        opt.com4.clone().unwrap_or(SerialConfigCli::None),
400        if cfg!(guest_arch = "x86_64") {
401            "ttyS3"
402        } else {
403            "ttyAMA3"
404        },
405    )?;
406    let virtio_serial_cfg = setup_serial_virtio(
407        "virtio_serial",
408        opt.virtio_serial.clone().unwrap_or({
409            if virtio_console {
410                SerialConfigCli::Console
411            } else {
412                SerialConfigCli::None
413            }
414        }),
415        if opt.virtio_console_pci {
416            "hvc1"
417        } else {
418            "hvc0"
419        },
420    )?;
421    let with_vmbus_com1_serial = if let Some(vmbus_com1_cfg) = setup_serial(
422        "vmbus_com1",
423        opt.vmbus_com1_serial
424            .clone()
425            .unwrap_or(SerialConfigCli::None),
426        "vmbus_com1",
427    )? {
428        vmbus_devices.push((
429            openhcl_vtl,
430            VmbusSerialDeviceHandle {
431                port: VmbusSerialPort::Com1,
432                backend: vmbus_com1_cfg,
433            }
434            .into_resource(),
435        ));
436        true
437    } else {
438        false
439    };
440    let with_vmbus_com2_serial = if let Some(vmbus_com2_cfg) = setup_serial(
441        "vmbus_com2",
442        opt.vmbus_com2_serial
443            .clone()
444            .unwrap_or(SerialConfigCli::None),
445        "vmbus_com2",
446    )? {
447        vmbus_devices.push((
448            openhcl_vtl,
449            VmbusSerialDeviceHandle {
450                port: VmbusSerialPort::Com2,
451                backend: vmbus_com2_cfg,
452            }
453            .into_resource(),
454        ));
455        true
456    } else {
457        false
458    };
459    let debugcon_cfg = setup_serial(
460        "debugcon",
461        opt.debugcon
462            .clone()
463            .map(|cfg| cfg.serial)
464            .unwrap_or(SerialConfigCli::None),
465        "debugcon",
466    )?;
467
468    let mut resources = VmResources::default();
469    let mut console_str = "";
470    if let Some(ConsoleState { device, input }) = console_state.into_inner() {
471        resources.console_in = Some(input);
472        console_str = device;
473    }
474
475    if opt.shared_memory {
476        tracing::warn!("--shared-memory/-M flag has no effect and will be removed");
477    }
478
479    const MAX_PROCESSOR_COUNT: u32 = 1024;
480
481    if opt.processors == 0 || opt.processors > MAX_PROCESSOR_COUNT {
482        bail!("invalid proc count: {}", opt.processors);
483    }
484
485    // Total SCSI channel count should not exceed the processor count
486    // (at most, one channel per VP).
487    if opt.scsi_sub_channels > (MAX_PROCESSOR_COUNT - 1) as u16 {
488        bail!(
489            "invalid SCSI sub-channel count: requested {}, max {}",
490            opt.scsi_sub_channels,
491            MAX_PROCESSOR_COUNT - 1
492        );
493    }
494
495    let with_get = opt.get || (opt.vtl2 && !opt.no_get);
496
497    let mut storage = storage_builder::StorageBuilder::new(with_get.then_some(openhcl_vtl));
498    for &cli_args::DiskCli {
499        vtl,
500        ref kind,
501        read_only,
502        is_dvd,
503        underhill,
504    } in &opt.disk
505    {
506        storage.add(
507            vtl,
508            underhill,
509            storage_builder::DiskLocation::Scsi(None),
510            kind,
511            is_dvd,
512            read_only,
513        )?;
514    }
515
516    for &cli_args::IdeDiskCli {
517        ref kind,
518        read_only,
519        channel,
520        device,
521        is_dvd,
522    } in &opt.ide
523    {
524        storage.add(
525            DeviceVtl::Vtl0,
526            None,
527            storage_builder::DiskLocation::Ide(channel, device),
528            kind,
529            is_dvd,
530            read_only,
531        )?;
532    }
533
534    for &cli_args::DiskCli {
535        vtl,
536        ref kind,
537        read_only,
538        is_dvd,
539        underhill,
540    } in &opt.nvme
541    {
542        storage.add(
543            vtl,
544            underhill,
545            storage_builder::DiskLocation::Nvme(None),
546            kind,
547            is_dvd,
548            read_only,
549        )?;
550    }
551
552    let floppy_disks: Vec<_> = opt
553        .floppy
554        .iter()
555        .map(|disk| -> anyhow::Result<_> {
556            let &cli_args::FloppyDiskCli {
557                ref kind,
558                read_only,
559            } = disk;
560            Ok(FloppyDiskConfig {
561                disk_type: disk_open(kind, read_only)?,
562                read_only,
563            })
564        })
565        .collect::<Result<Vec<_>, _>>()?;
566
567    let mut mana_nics = [(); 3].map(|()| None);
568    let mut underhill_nics = Vec::new();
569    let mut vpci_devices = Vec::new();
570
571    let mut nic_index = 0;
572    for cli_cfg in &opt.net {
573        let vport = parse_endpoint(cli_cfg, &mut nic_index, &mut resources)?;
574        if cli_cfg.underhill {
575            if !opt.no_alias_map {
576                anyhow::bail!("must specify --no-alias-map to offer NICs to VTL2");
577            }
578            let mana = mana_nics[openhcl_vtl as usize].get_or_insert_with(|| {
579                let vpci_instance_id = Guid::new_random();
580                underhill_nics.push(vtl2_settings_proto::NicDeviceLegacy {
581                    instance_id: vpci_instance_id.to_string(),
582                    subordinate_instance_id: None,
583                    max_sub_channels: None,
584                });
585                (vpci_instance_id, GdmaDeviceHandle { vports: Vec::new() })
586            });
587            mana.1.vports.push(VportDefinition {
588                mac_address: vport.mac_address,
589                endpoint: vport.endpoint,
590            });
591        } else {
592            vmbus_devices.push(vport.into_netvsp_handle());
593        }
594    }
595
596    if opt.nic {
597        let nic_config = parse_endpoint(
598            &NicConfigCli {
599                vtl: DeviceVtl::Vtl0,
600                endpoint: EndpointConfigCli::Consomme { cidr: None },
601                max_queues: None,
602                underhill: false,
603            },
604            &mut nic_index,
605            &mut resources,
606        )?;
607        vmbus_devices.push(nic_config.into_netvsp_handle());
608    }
609
610    if opt.mcr {
611        tracing::info!("Instantiating MCR controller");
612
613        // Arbitrary but constant instance ID to be consistent across boots.
614        const MCR_INSTANCE_ID: Guid = guid::guid!("07effd8f-7501-426c-a947-d8345f39113d");
615
616        vpci_devices.push(VpciDeviceConfig {
617            vtl: DeviceVtl::Vtl0,
618            instance_id: MCR_INSTANCE_ID,
619            resource: mcr_resources::McrControllerHandle {
620                instance_id: MCR_INSTANCE_ID,
621            }
622            .into_resource(),
623        });
624    }
625
626    #[cfg(windows)]
627    let mut kernel_vmnics = Vec::new();
628    #[cfg(windows)]
629    for (index, switch_id) in opt.kernel_vmnic.iter().enumerate() {
630        // Pick a random MAC address.
631        let mut mac_address = [0x00, 0x15, 0x5D, 0, 0, 0];
632        getrandom::fill(&mut mac_address[3..]).expect("rng failure");
633
634        // Pick a fixed instance ID based on the index.
635        const BASE_INSTANCE_ID: Guid = guid::guid!("00000000-435d-11ee-9f59-00155d5016fc");
636        let instance_id = Guid {
637            data1: index as u32,
638            ..BASE_INSTANCE_ID
639        };
640
641        let switch_id = if switch_id == "default" {
642            DEFAULT_SWITCH
643        } else {
644            switch_id
645        };
646        let (port_id, port) = new_switch_port(switch_id)?;
647        resources.switch_ports.push(port);
648
649        kernel_vmnics.push(hvlite_defs::config::KernelVmNicConfig {
650            instance_id,
651            mac_address: mac_address.into(),
652            switch_port_id: port_id,
653        });
654    }
655
656    for vport in &opt.mana {
657        let vport = parse_endpoint(vport, &mut nic_index, &mut resources)?;
658        mana_nics[vport.vtl as usize]
659            .get_or_insert_with(|| (Guid::new_random(), GdmaDeviceHandle { vports: Vec::new() }))
660            .1
661            .vports
662            .push(VportDefinition {
663                mac_address: vport.mac_address,
664                endpoint: vport.endpoint,
665            });
666    }
667
668    vpci_devices.extend(mana_nics.into_iter().enumerate().filter_map(|(vtl, nic)| {
669        nic.map(|(instance_id, handle)| VpciDeviceConfig {
670            vtl: match vtl {
671                0 => DeviceVtl::Vtl0,
672                1 => DeviceVtl::Vtl1,
673                2 => DeviceVtl::Vtl2,
674                _ => unreachable!(),
675            },
676            instance_id,
677            resource: handle.into_resource(),
678        })
679    }));
680
681    #[cfg(windows)]
682    let vpci_resources: Vec<_> = opt
683        .device
684        .iter()
685        .map(|path| -> anyhow::Result<_> {
686            Ok(virt_whp::device::DeviceHandle(
687                whp::VpciResource::new(
688                    None,
689                    Default::default(),
690                    &whp::VpciResourceDescriptor::Sriov(path, 0, 0),
691                )
692                .with_context(|| format!("opening PCI device {}", path))?,
693            ))
694        })
695        .collect::<Result<_, _>>()?;
696
697    // Create a vmbusproxy handle if needed by any devices.
698    #[cfg(windows)]
699    let vmbusproxy_handle = if !kernel_vmnics.is_empty() {
700        Some(vmbus_proxy::ProxyHandle::new().context("failed to open vmbusproxy handle")?)
701    } else {
702        None
703    };
704
705    let framebuffer = if opt.gfx || opt.vtl2_gfx || opt.vnc || opt.pcat {
706        let vram = alloc_shared_memory(FRAMEBUFFER_SIZE)?;
707        let (fb, fba) =
708            framebuffer::framebuffer(vram, FRAMEBUFFER_SIZE, 0).context("creating framebuffer")?;
709        resources.framebuffer_access = Some(fba);
710        Some(fb)
711    } else {
712        None
713    };
714
715    let is_arm = cfg!(guest_arch = "aarch64");
716    let is_x86 = cfg!(guest_arch = "x86_64");
717
718    let load_mode;
719    let with_hv;
720
721    let any_serial_configured = serial0_cfg.is_some()
722        || serial1_cfg.is_some()
723        || serial2_cfg.is_some()
724        || serial3_cfg.is_some();
725
726    let has_com3 = serial2_cfg.is_some();
727
728    let mut chipset = VmManifestBuilder::new(
729        if opt.igvm.is_some() {
730            BaseChipsetType::HclHost
731        } else if opt.pcat {
732            BaseChipsetType::HypervGen1
733        } else if opt.uefi {
734            BaseChipsetType::HypervGen2Uefi
735        } else if opt.hv {
736            BaseChipsetType::HyperVGen2LinuxDirect
737        } else {
738            BaseChipsetType::UnenlightenedLinuxDirect
739        },
740        if is_x86 {
741            MachineArch::X86_64
742        } else {
743            MachineArch::Aarch64
744        },
745    );
746
747    if framebuffer.is_some() {
748        chipset = chipset.with_framebuffer();
749    }
750    if opt.guest_watchdog {
751        chipset = chipset.with_guest_watchdog();
752    }
753    if any_serial_configured {
754        chipset = chipset.with_serial([serial0_cfg, serial1_cfg, serial2_cfg, serial3_cfg]);
755    }
756    if opt.battery {
757        let (tx, rx) = mesh::channel();
758        tx.send(HostBatteryUpdate::default_present());
759        chipset = chipset.with_battery(rx);
760    }
761    if let Some(cfg) = &opt.debugcon {
762        chipset = chipset.with_debugcon(
763            debugcon_cfg.unwrap_or_else(|| DisconnectedSerialBackendHandle.into_resource()),
764            cfg.port,
765        );
766    }
767
768    let VmChipsetResult {
769        chipset,
770        mut chipset_devices,
771    } = chipset
772        .build()
773        .context("failed to build chipset configuration")?;
774
775    if let Some(path) = &opt.igvm {
776        let file = fs_err::File::open(path)
777            .context("failed to open igvm file")?
778            .into();
779        let cmdline = opt.cmdline.join(" ");
780        with_hv = true;
781
782        load_mode = LoadMode::Igvm {
783            file,
784            cmdline,
785            vtl2_base_address: opt.igvm_vtl2_relocation_type,
786            com_serial: has_com3.then(|| SerialInformation {
787                io_port: ComPort::Com3.io_port(),
788                irq: ComPort::Com3.irq().into(),
789            }),
790        };
791    } else if opt.pcat {
792        // Emit a nice error early instead of complaining about missing firmware.
793        if !is_x86 {
794            anyhow::bail!("pcat not supported on this architecture");
795        }
796        with_hv = true;
797
798        let firmware = hvlite_pcat_locator::find_pcat_bios(opt.pcat_firmware.as_deref())?;
799        load_mode = LoadMode::Pcat {
800            firmware,
801            boot_order: opt
802                .pcat_boot_order
803                .map(|x| x.0)
804                .unwrap_or(DEFAULT_PCAT_BOOT_ORDER),
805        };
806    } else if opt.uefi {
807        use hvlite_defs::config::UefiConsoleMode;
808
809        with_hv = true;
810
811        let firmware = fs_err::File::open(
812            (opt.uefi_firmware.0)
813                .as_ref()
814                .context("must provide uefi firmware when booting with uefi")?,
815        )
816        .context("failed to open uefi firmware")?;
817
818        // TODO: It would be better to default memory protections to on, but currently Linux does not boot via UEFI due to what
819        //       appears to be a GRUB memory protection fault. Memory protections are therefore only enabled if configured.
820        load_mode = LoadMode::Uefi {
821            firmware: firmware.into(),
822            enable_debugging: opt.uefi_debug,
823            enable_memory_protections: opt.uefi_enable_memory_protections,
824            disable_frontpage: opt.disable_frontpage,
825            enable_tpm: opt.tpm,
826            enable_battery: opt.battery,
827            enable_serial: any_serial_configured,
828            enable_vpci_boot: false,
829            uefi_console_mode: opt.uefi_console_mode.map(|m| match m {
830                UefiConsoleModeCli::Default => UefiConsoleMode::Default,
831                UefiConsoleModeCli::Com1 => UefiConsoleMode::Com1,
832                UefiConsoleModeCli::Com2 => UefiConsoleMode::Com2,
833                UefiConsoleModeCli::None => UefiConsoleMode::None,
834            }),
835            default_boot_always_attempt: opt.default_boot_always_attempt,
836        };
837    } else {
838        // Linux Direct
839        let mut cmdline = "panic=-1 debug".to_string();
840
841        with_hv = opt.hv;
842        if with_hv {
843            cmdline += " pci=off";
844        }
845
846        if !console_str.is_empty() {
847            let _ = write!(&mut cmdline, " console={}", console_str);
848        }
849        if opt.gfx {
850            cmdline += " console=tty";
851        }
852        for extra in &opt.cmdline {
853            let _ = write!(&mut cmdline, " {}", extra);
854        }
855
856        let kernel = fs_err::File::open(
857            (opt.kernel.0)
858                .as_ref()
859                .context("must provide kernel when booting with linux direct")?,
860        )
861        .context("failed to open kernel")?;
862        let initrd = (opt.initrd.0)
863            .as_ref()
864            .map(fs_err::File::open)
865            .transpose()
866            .context("failed to open initrd")?;
867
868        let custom_dsdt = match &opt.custom_dsdt {
869            Some(path) => {
870                let mut v = Vec::new();
871                fs_err::File::open(path)
872                    .context("failed to open custom dsdt")?
873                    .read_to_end(&mut v)
874                    .context("failed to read custom dsdt")?;
875                Some(v)
876            }
877            None => None,
878        };
879
880        load_mode = LoadMode::Linux {
881            kernel: kernel.into(),
882            initrd: initrd.map(Into::into),
883            cmdline,
884            custom_dsdt,
885            enable_serial: any_serial_configured,
886        };
887    }
888
889    let mut vmgs = Some(if let Some(VmgsCli { kind, provision }) = &opt.vmgs {
890        let disk = disk_open(kind, false).context("failed to open vmgs disk")?;
891        match provision {
892            ProvisionVmgs::OnEmpty => VmgsResource::Disk(disk),
893            ProvisionVmgs::OnFailure => VmgsResource::ReprovisionOnFailure(disk),
894            ProvisionVmgs::True => VmgsResource::Reprovision(disk),
895        }
896    } else {
897        VmgsResource::Ephemeral
898    });
899
900    if with_get && with_hv {
901        let vtl2_settings = vtl2_settings_proto::Vtl2Settings {
902            version: vtl2_settings_proto::vtl2_settings_base::Version::V1.into(),
903            fixed: Some(Default::default()),
904            dynamic: Some(vtl2_settings_proto::Vtl2SettingsDynamic {
905                storage_controllers: storage.build_underhill(),
906                nic_devices: underhill_nics,
907            }),
908            namespace_settings: Vec::default(),
909        };
910
911        let (send, guest_request_recv) = mesh::channel();
912        resources.ged_rpc = Some(send);
913
914        let vmgs = vmgs.take().unwrap();
915
916        vmbus_devices.extend([
917            (
918                openhcl_vtl,
919                get_resources::gel::GuestEmulationLogHandle.into_resource(),
920            ),
921            (
922                openhcl_vtl,
923                get_resources::ged::GuestEmulationDeviceHandle {
924                    firmware: if opt.pcat {
925                        get_resources::ged::GuestFirmwareConfig::Pcat {
926                            boot_order: opt
927                                .pcat_boot_order
928                                .map_or(DEFAULT_PCAT_BOOT_ORDER, |x| x.0)
929                                .map(|x| match x {
930                                    hvlite_defs::config::PcatBootDevice::Floppy => {
931                                        get_resources::ged::PcatBootDevice::Floppy
932                                    }
933                                    hvlite_defs::config::PcatBootDevice::HardDrive => {
934                                        get_resources::ged::PcatBootDevice::HardDrive
935                                    }
936                                    hvlite_defs::config::PcatBootDevice::Optical => {
937                                        get_resources::ged::PcatBootDevice::Optical
938                                    }
939                                    hvlite_defs::config::PcatBootDevice::Network => {
940                                        get_resources::ged::PcatBootDevice::Network
941                                    }
942                                }),
943                        }
944                    } else {
945                        use get_resources::ged::UefiConsoleMode;
946
947                        get_resources::ged::GuestFirmwareConfig::Uefi {
948                            enable_vpci_boot: storage.has_vtl0_nvme(),
949                            firmware_debug: opt.uefi_debug,
950                            disable_frontpage: opt.disable_frontpage,
951                            console_mode: match opt.uefi_console_mode.unwrap_or(UefiConsoleModeCli::Default) {
952                                UefiConsoleModeCli::Default => UefiConsoleMode::Default,
953                                UefiConsoleModeCli::Com1 => UefiConsoleMode::COM1,
954                                UefiConsoleModeCli::Com2 => UefiConsoleMode::COM2,
955                                UefiConsoleModeCli::None => UefiConsoleMode::None,
956                            },
957                            default_boot_always_attempt: opt.default_boot_always_attempt,
958                        }
959                    },
960                    com1: with_vmbus_com1_serial,
961                    com2: with_vmbus_com2_serial,
962                    vtl2_settings: Some(prost::Message::encode_to_vec(&vtl2_settings)),
963                    vmbus_redirection: opt.vmbus_redirect,
964                    vmgs,
965                    framebuffer: opt
966                        .vtl2_gfx
967                        .then(|| SharedFramebufferHandle.into_resource()),
968                    guest_request_recv,
969                    enable_tpm: opt.tpm,
970                    firmware_event_send: None,
971                    secure_boot_enabled: opt.secure_boot,
972                    secure_boot_template: match opt.secure_boot_template {
973                        Some(SecureBootTemplateCli::Windows) => {
974                            get_resources::ged::GuestSecureBootTemplateType::MicrosoftWindows
975                        },
976                        Some(SecureBootTemplateCli::UefiCa) => {
977                            get_resources::ged::GuestSecureBootTemplateType::MicrosoftUefiCertificateAuthority
978                        }
979                        None => {
980                            get_resources::ged::GuestSecureBootTemplateType::None
981                        },
982                    },
983                    enable_battery: opt.battery,
984                    no_persistent_secrets: true,
985                    igvm_attest_test_config: None,
986                }
987                .into_resource(),
988            ),
989        ]);
990    }
991
992    if opt.tpm && !opt.vtl2 {
993        let register_layout = if cfg!(guest_arch = "x86_64") {
994            TpmRegisterLayout::IoPort
995        } else {
996            TpmRegisterLayout::Mmio
997        };
998
999        let (ppi_store, nvram_store) = if opt.vmgs.is_some() {
1000            (
1001                VmgsFileHandle::new(vmgs_format::FileId::TPM_PPI, true).into_resource(),
1002                VmgsFileHandle::new(vmgs_format::FileId::TPM_NVRAM, true).into_resource(),
1003            )
1004        } else {
1005            (
1006                EphemeralNonVolatileStoreHandle.into_resource(),
1007                EphemeralNonVolatileStoreHandle.into_resource(),
1008            )
1009        };
1010
1011        chipset_devices.push(ChipsetDeviceHandle {
1012            name: "tpm".to_string(),
1013            resource: TpmDeviceHandle {
1014                ppi_store,
1015                nvram_store,
1016                refresh_tpm_seeds: false,
1017                ak_cert_type: tpm_resources::TpmAkCertTypeResource::None,
1018                register_layout,
1019                guest_secret_key: None,
1020                logger: None,
1021                is_confidential_vm: false,
1022            }
1023            .into_resource(),
1024        });
1025    }
1026
1027    let custom_uefi_vars = {
1028        use firmware_uefi_custom_vars::CustomVars;
1029
1030        // load base vars from specified template, or use an empty set of base
1031        // vars if none was specified.
1032        let base_vars = match opt.secure_boot_template {
1033            Some(template) => {
1034                if is_x86 {
1035                    match template {
1036                        SecureBootTemplateCli::Windows => {
1037                            hyperv_secure_boot_templates::x64::microsoft_windows()
1038                        }
1039                        SecureBootTemplateCli::UefiCa => {
1040                            hyperv_secure_boot_templates::x64::microsoft_uefi_ca()
1041                        }
1042                    }
1043                } else if is_arm {
1044                    match template {
1045                        SecureBootTemplateCli::Windows => {
1046                            hyperv_secure_boot_templates::aarch64::microsoft_windows()
1047                        }
1048                        SecureBootTemplateCli::UefiCa => {
1049                            hyperv_secure_boot_templates::aarch64::microsoft_uefi_ca()
1050                        }
1051                    }
1052                } else {
1053                    anyhow::bail!("no secure boot template for current guest_arch")
1054                }
1055            }
1056            None => CustomVars::default(),
1057        };
1058
1059        // TODO: fallback to VMGS read if no command line flag was given
1060
1061        let custom_uefi_json_data = match &opt.custom_uefi_json {
1062            Some(file) => Some(fs_err::read(file).context("opening custom uefi json file")?),
1063            None => None,
1064        };
1065
1066        // obtain the final custom uefi vars by applying the delta onto the base vars
1067        match custom_uefi_json_data {
1068            Some(data) => {
1069                let delta = hyperv_uefi_custom_vars_json::load_delta_from_json(&data)?;
1070                base_vars.apply_delta(delta)?
1071            }
1072            None => base_vars,
1073        }
1074    };
1075
1076    let vga_firmware = if opt.pcat {
1077        Some(hvlite_pcat_locator::find_svga_bios(
1078            opt.vga_firmware.as_deref(),
1079        )?)
1080    } else {
1081        None
1082    };
1083
1084    if opt.gfx {
1085        vmbus_devices.extend([
1086            (
1087                DeviceVtl::Vtl0,
1088                SynthVideoHandle {
1089                    framebuffer: SharedFramebufferHandle.into_resource(),
1090                }
1091                .into_resource(),
1092            ),
1093            (
1094                DeviceVtl::Vtl0,
1095                SynthKeyboardHandle {
1096                    source: MultiplexedInputHandle {
1097                        // Save 0 for PS/2
1098                        elevation: 1,
1099                    }
1100                    .into_resource(),
1101                }
1102                .into_resource(),
1103            ),
1104            (
1105                DeviceVtl::Vtl0,
1106                SynthMouseHandle {
1107                    source: MultiplexedInputHandle {
1108                        // Save 0 for PS/2
1109                        elevation: 1,
1110                    }
1111                    .into_resource(),
1112                }
1113                .into_resource(),
1114            ),
1115        ]);
1116    }
1117
1118    let vsock_listener = |path: Option<&str>| -> anyhow::Result<_> {
1119        if let Some(path) = path {
1120            cleanup_socket(path.as_ref());
1121            let listener = unix_socket::UnixListener::bind(path)
1122                .with_context(|| format!("failed to bind to hybrid vsock path: {}", path))?;
1123            Ok(Some(listener))
1124        } else {
1125            Ok(None)
1126        }
1127    };
1128
1129    let vtl0_vsock_listener = vsock_listener(opt.vsock_path.as_deref())?;
1130    let vtl2_vsock_listener = vsock_listener(opt.vtl2_vsock_path.as_deref())?;
1131
1132    // If VTL2 is enabled, and we are not in VTL2 self allocate mode, provide an
1133    // mmio gap for VTL2.
1134    let mmio_gaps = if opt.vtl2
1135        && !matches!(
1136            opt.igvm_vtl2_relocation_type,
1137            Vtl2BaseAddressType::Vtl2Allocate { .. },
1138        ) {
1139        if is_x86 {
1140            DEFAULT_MMIO_GAPS_X86_WITH_VTL2.into()
1141        } else {
1142            DEFAULT_MMIO_GAPS_AARCH64_WITH_VTL2.into()
1143        }
1144    } else if is_x86 {
1145        DEFAULT_MMIO_GAPS_X86.into()
1146    } else {
1147        DEFAULT_MMIO_GAPS_AARCH64.into()
1148    };
1149
1150    if let Some(path) = &opt.openhcl_dump_path {
1151        let (resource, task) = spawn_dump_handler(&spawner, path.clone(), None);
1152        task.detach();
1153        vmbus_devices.push((openhcl_vtl, resource));
1154    }
1155
1156    #[cfg(guest_arch = "aarch64")]
1157    let topology_arch = hvlite_defs::config::ArchTopologyConfig::Aarch64(
1158        hvlite_defs::config::Aarch64TopologyConfig {
1159            // TODO: allow this to be configured from the command line
1160            gic_config: None,
1161            pmu_gsiv: hvlite_defs::config::PmuGsivConfig::Platform,
1162        },
1163    );
1164    #[cfg(guest_arch = "x86_64")]
1165    let topology_arch =
1166        hvlite_defs::config::ArchTopologyConfig::X86(hvlite_defs::config::X86TopologyConfig {
1167            apic_id_offset: opt.apic_id_offset,
1168            x2apic: opt.x2apic,
1169        });
1170
1171    let with_isolation = if let Some(isolation) = &opt.isolation {
1172        // TODO: For now, isolation is only supported with VTL2.
1173        if !opt.vtl2 {
1174            anyhow::bail!("isolation is only currently supported with vtl2");
1175        }
1176
1177        // TODO: Alias map support is not yet implement with isolation.
1178        if !opt.no_alias_map {
1179            anyhow::bail!("alias map not supported with isolation");
1180        }
1181
1182        match isolation {
1183            cli_args::IsolationCli::Vbs => Some(hvlite_defs::config::IsolationType::Vbs),
1184        }
1185    } else {
1186        None
1187    };
1188
1189    if with_hv {
1190        let (shutdown_send, shutdown_recv) = mesh::channel();
1191        resources.shutdown_ic = Some(shutdown_send);
1192        let (kvp_send, kvp_recv) = mesh::channel();
1193        resources.kvp_ic = Some(kvp_send);
1194        vmbus_devices.extend(
1195            [
1196                hyperv_ic_resources::shutdown::ShutdownIcHandle {
1197                    recv: shutdown_recv,
1198                }
1199                .into_resource(),
1200                hyperv_ic_resources::kvp::KvpIcHandle { recv: kvp_recv }.into_resource(),
1201                hyperv_ic_resources::timesync::TimesyncIcHandle.into_resource(),
1202            ]
1203            .map(|r| (DeviceVtl::Vtl0, r)),
1204        );
1205    }
1206
1207    if let Some(hive_path) = &opt.imc {
1208        let file = fs_err::File::open(hive_path).context("failed to open imc hive")?;
1209        vmbus_devices.push((
1210            DeviceVtl::Vtl0,
1211            vmbfs_resources::VmbfsImcDeviceHandle { file: file.into() }.into_resource(),
1212        ));
1213    }
1214
1215    let mut virtio_devices = Vec::new();
1216    let mut add_virtio_device = |bus, resource: Resource<VirtioDeviceHandle>| {
1217        let bus = match bus {
1218            VirtioBusCli::Auto => {
1219                // Use VPCI when possible (currently only on Windows and macOS due
1220                // to KVM backend limitations).
1221                if with_hv && (cfg!(windows) || cfg!(target_os = "macos")) {
1222                    None
1223                } else {
1224                    Some(VirtioBus::Pci)
1225                }
1226            }
1227            VirtioBusCli::Mmio => Some(VirtioBus::Mmio),
1228            VirtioBusCli::Pci => Some(VirtioBus::Pci),
1229            VirtioBusCli::Vpci => None,
1230        };
1231        if let Some(bus) = bus {
1232            virtio_devices.push((bus, resource));
1233        } else {
1234            vpci_devices.push(VpciDeviceConfig {
1235                vtl: DeviceVtl::Vtl0,
1236                instance_id: Guid::new_random(),
1237                resource: VirtioPciDeviceHandle(resource).into_resource(),
1238            });
1239        }
1240    };
1241
1242    for cli_cfg in &opt.virtio_net {
1243        if cli_cfg.underhill {
1244            anyhow::bail!("use --net uh:[...] to add underhill NICs")
1245        }
1246        let vport = parse_endpoint(cli_cfg, &mut nic_index, &mut resources)?;
1247        add_virtio_device(
1248            VirtioBusCli::Auto,
1249            virtio_resources::net::VirtioNetHandle {
1250                max_queues: vport.max_queues,
1251                mac_address: vport.mac_address,
1252                endpoint: vport.endpoint,
1253            }
1254            .into_resource(),
1255        );
1256    }
1257
1258    for args in &opt.virtio_fs {
1259        add_virtio_device(
1260            opt.virtio_fs_bus,
1261            virtio_resources::fs::VirtioFsHandle {
1262                tag: args.tag.clone(),
1263                fs: virtio_resources::fs::VirtioFsBackend::HostFs {
1264                    root_path: args.path.clone(),
1265                    mount_options: args.options.clone(),
1266                },
1267            }
1268            .into_resource(),
1269        );
1270    }
1271
1272    for args in &opt.virtio_fs_shmem {
1273        add_virtio_device(
1274            opt.virtio_fs_bus,
1275            virtio_resources::fs::VirtioFsHandle {
1276                tag: args.tag.clone(),
1277                fs: virtio_resources::fs::VirtioFsBackend::SectionFs {
1278                    root_path: args.path.clone(),
1279                },
1280            }
1281            .into_resource(),
1282        );
1283    }
1284
1285    for args in &opt.virtio_9p {
1286        add_virtio_device(
1287            VirtioBusCli::Auto,
1288            virtio_resources::p9::VirtioPlan9Handle {
1289                tag: args.tag.clone(),
1290                root_path: args.path.clone(),
1291                debug: opt.virtio_9p_debug,
1292            }
1293            .into_resource(),
1294        );
1295    }
1296
1297    if let Some(path) = &opt.virtio_pmem {
1298        add_virtio_device(
1299            VirtioBusCli::Auto,
1300            virtio_resources::pmem::VirtioPmemHandle { path: path.clone() }.into_resource(),
1301        );
1302    }
1303
1304    let mut cfg = Config {
1305        chipset,
1306        load_mode,
1307        floppy_disks,
1308        vpci_devices,
1309        ide_disks: Vec::new(),
1310        memory: MemoryConfig {
1311            mem_size: opt.memory,
1312            mmio_gaps,
1313            prefetch_memory: opt.prefetch,
1314        },
1315        processor_topology: ProcessorTopologyConfig {
1316            proc_count: opt.processors,
1317            vps_per_socket: opt.vps_per_socket,
1318            enable_smt: match opt.smt {
1319                cli_args::SmtConfigCli::Auto => None,
1320                cli_args::SmtConfigCli::Force => Some(true),
1321                cli_args::SmtConfigCli::Off => Some(false),
1322            },
1323            arch: Some(topology_arch),
1324        },
1325        hypervisor: HypervisorConfig {
1326            with_hv,
1327            with_vtl2: opt.vtl2.then_some(Vtl2Config {
1328                vtl0_alias_map: !opt.no_alias_map,
1329                late_map_vtl0_memory: match opt.late_map_vtl0_policy {
1330                    cli_args::Vtl0LateMapPolicyCli::Off => None,
1331                    cli_args::Vtl0LateMapPolicyCli::Log => Some(LateMapVtl0MemoryPolicy::Log),
1332                    cli_args::Vtl0LateMapPolicyCli::Halt => Some(LateMapVtl0MemoryPolicy::Halt),
1333                    cli_args::Vtl0LateMapPolicyCli::Exception => {
1334                        Some(LateMapVtl0MemoryPolicy::InjectException)
1335                    }
1336                },
1337            }),
1338            with_isolation,
1339            user_mode_hv_enlightenments: opt.no_enlightenments,
1340            user_mode_apic: opt.user_mode_apic,
1341        },
1342        #[cfg(windows)]
1343        kernel_vmnics,
1344        input: mesh::Receiver::new(),
1345        framebuffer,
1346        vga_firmware,
1347        vtl2_gfx: opt.vtl2_gfx,
1348        virtio_console_pci: opt.virtio_console_pci,
1349        virtio_serial: virtio_serial_cfg,
1350        virtio_devices,
1351        vmbus: with_hv.then_some(VmbusConfig {
1352            vsock_listener: vtl0_vsock_listener,
1353            vsock_path: opt.vsock_path.clone(),
1354            vtl2_redirect: opt.vmbus_redirect,
1355            vmbus_max_version: opt.vmbus_max_version,
1356            #[cfg(windows)]
1357            vmbusproxy_handle,
1358        }),
1359        vtl2_vmbus: (with_hv && opt.vtl2).then_some(VmbusConfig {
1360            vsock_listener: vtl2_vsock_listener,
1361            vsock_path: opt.vtl2_vsock_path.clone(),
1362            ..Default::default()
1363        }),
1364        vmbus_devices,
1365        chipset_devices,
1366        #[cfg(windows)]
1367        vpci_resources,
1368        vmgs,
1369        secure_boot_enabled: opt.secure_boot,
1370        custom_uefi_vars,
1371        firmware_event_send: None,
1372        debugger_rpc: None,
1373        generation_id_recv: None,
1374        rtc_delta_milliseconds: 0,
1375        automatic_guest_reset: !opt.halt_on_reset,
1376    };
1377
1378    storage.build_config(&mut cfg, &mut resources, opt.scsi_sub_channels)?;
1379    Ok((cfg, resources))
1380}
1381
1382/// Gets the terminal to use for externally launched console windows.
1383fn openvmm_terminal_app() -> Option<PathBuf> {
1384    std::env::var_os("OPENVMM_TERM")
1385        .or_else(|| std::env::var_os("HVLITE_TERM"))
1386        .map(Into::into)
1387}
1388
1389// Tries to remove `path` if it is confirmed to be a Unix socket.
1390fn cleanup_socket(path: &Path) {
1391    #[cfg(windows)]
1392    let is_socket = pal::windows::fs::is_unix_socket(path).unwrap_or(false);
1393    #[cfg(not(windows))]
1394    let is_socket = path
1395        .metadata()
1396        .is_ok_and(|meta| std::os::unix::fs::FileTypeExt::is_socket(&meta.file_type()));
1397
1398    if is_socket {
1399        let _ = std::fs::remove_file(path);
1400    }
1401}
1402
1403#[cfg(windows)]
1404const DEFAULT_SWITCH: &str = "C08CB7B8-9B3C-408E-8E30-5E16A3AEB444";
1405
1406#[cfg(windows)]
1407fn new_switch_port(
1408    switch_id: &str,
1409) -> anyhow::Result<(
1410    hvlite_defs::config::SwitchPortId,
1411    vmswitch::kernel::SwitchPort,
1412)> {
1413    let id = vmswitch::kernel::SwitchPortId {
1414        switch: switch_id.parse().context("invalid switch id")?,
1415        port: Guid::new_random(),
1416    };
1417    let _ = vmswitch::hcn::Network::open(&id.switch)
1418        .with_context(|| format!("could not find switch {}", id.switch))?;
1419
1420    let port = vmswitch::kernel::SwitchPort::new(&id).context("failed to create switch port")?;
1421
1422    let id = hvlite_defs::config::SwitchPortId {
1423        switch: id.switch,
1424        port: id.port,
1425    };
1426    Ok((id, port))
1427}
1428
1429fn parse_endpoint(
1430    cli_cfg: &NicConfigCli,
1431    index: &mut usize,
1432    resources: &mut VmResources,
1433) -> anyhow::Result<NicConfig> {
1434    let _ = resources;
1435    let endpoint = match &cli_cfg.endpoint {
1436        EndpointConfigCli::Consomme { cidr } => {
1437            net_backend_resources::consomme::ConsommeHandle { cidr: cidr.clone() }.into_resource()
1438        }
1439        EndpointConfigCli::None => net_backend_resources::null::NullHandle.into_resource(),
1440        EndpointConfigCli::Dio { id } => {
1441            #[cfg(windows)]
1442            {
1443                let (port_id, port) = new_switch_port(id.as_deref().unwrap_or(DEFAULT_SWITCH))?;
1444                resources.switch_ports.push(port);
1445                net_backend_resources::dio::WindowsDirectIoHandle {
1446                    switch_port_id: net_backend_resources::dio::SwitchPortId {
1447                        switch: port_id.switch,
1448                        port: port_id.port,
1449                    },
1450                }
1451                .into_resource()
1452            }
1453
1454            #[cfg(not(windows))]
1455            {
1456                let _ = id;
1457                bail!("cannot use dio on non-windows platforms")
1458            }
1459        }
1460        EndpointConfigCli::Tap { name } => {
1461            net_backend_resources::tap::TapHandle { name: name.clone() }.into_resource()
1462        }
1463    };
1464
1465    // Pick a random MAC address.
1466    let mut mac_address = [0x00, 0x15, 0x5D, 0, 0, 0];
1467    getrandom::fill(&mut mac_address[3..]).expect("rng failure");
1468
1469    // Pick a fixed instance ID based on the index.
1470    const BASE_INSTANCE_ID: Guid = guid::guid!("00000000-da43-11ed-936a-00155d6db52f");
1471    let instance_id = Guid {
1472        data1: *index as u32,
1473        ..BASE_INSTANCE_ID
1474    };
1475    *index += 1;
1476
1477    Ok(NicConfig {
1478        vtl: cli_cfg.vtl,
1479        instance_id,
1480        endpoint,
1481        mac_address: mac_address.into(),
1482        max_queues: cli_cfg.max_queues,
1483    })
1484}
1485
1486#[derive(Debug)]
1487struct NicConfig {
1488    vtl: DeviceVtl,
1489    instance_id: Guid,
1490    mac_address: MacAddress,
1491    endpoint: Resource<NetEndpointHandleKind>,
1492    max_queues: Option<u16>,
1493}
1494
1495impl NicConfig {
1496    fn into_netvsp_handle(self) -> (DeviceVtl, Resource<VmbusDeviceHandleKind>) {
1497        (
1498            self.vtl,
1499            netvsp_resources::NetvspHandle {
1500                instance_id: self.instance_id,
1501                mac_address: self.mac_address,
1502                endpoint: self.endpoint,
1503                max_queues: self.max_queues,
1504            }
1505            .into_resource(),
1506        )
1507    }
1508}
1509
1510enum LayerOrDisk {
1511    Layer(DiskLayerDescription),
1512    Disk(Resource<DiskHandleKind>),
1513}
1514
1515fn disk_open(disk_cli: &DiskCliKind, read_only: bool) -> anyhow::Result<Resource<DiskHandleKind>> {
1516    let mut layers = Vec::new();
1517    disk_open_inner(disk_cli, read_only, &mut layers)?;
1518    if layers.len() == 1 && matches!(layers[0], LayerOrDisk::Disk(_)) {
1519        let LayerOrDisk::Disk(disk) = layers.pop().unwrap() else {
1520            unreachable!()
1521        };
1522        Ok(disk)
1523    } else {
1524        Ok(Resource::new(disk_backend_resources::LayeredDiskHandle {
1525            layers: layers
1526                .into_iter()
1527                .map(|layer| match layer {
1528                    LayerOrDisk::Layer(layer) => layer,
1529                    LayerOrDisk::Disk(disk) => DiskLayerDescription {
1530                        layer: DiskLayerHandle(disk).into_resource(),
1531                        read_cache: false,
1532                        write_through: false,
1533                    },
1534                })
1535                .collect(),
1536        }))
1537    }
1538}
1539
1540fn disk_open_inner(
1541    disk_cli: &DiskCliKind,
1542    read_only: bool,
1543    layers: &mut Vec<LayerOrDisk>,
1544) -> anyhow::Result<()> {
1545    fn layer<T: IntoResource<DiskLayerHandleKind>>(layer: T) -> LayerOrDisk {
1546        LayerOrDisk::Layer(layer.into_resource().into())
1547    }
1548    fn disk<T: IntoResource<DiskHandleKind>>(disk: T) -> LayerOrDisk {
1549        LayerOrDisk::Disk(disk.into_resource())
1550    }
1551    match disk_cli {
1552        &DiskCliKind::Memory(len) => {
1553            layers.push(layer(RamDiskLayerHandle { len: Some(len) }));
1554        }
1555        DiskCliKind::File {
1556            path,
1557            create_with_len,
1558        } => layers.push(LayerOrDisk::Disk(if let Some(size) = create_with_len {
1559            create_disk_type(path, *size)
1560                .with_context(|| format!("failed to create {}", path.display()))?
1561        } else {
1562            open_disk_type(path, read_only)
1563                .with_context(|| format!("failed to open {}", path.display()))?
1564        })),
1565        DiskCliKind::Blob { kind, url } => {
1566            layers.push(disk(disk_backend_resources::BlobDiskHandle {
1567                url: url.to_owned(),
1568                format: match kind {
1569                    cli_args::BlobKind::Flat => disk_backend_resources::BlobDiskFormat::Flat,
1570                    cli_args::BlobKind::Vhd1 => disk_backend_resources::BlobDiskFormat::FixedVhd1,
1571                },
1572            }))
1573        }
1574        DiskCliKind::MemoryDiff(inner) => {
1575            layers.push(layer(RamDiskLayerHandle { len: None }));
1576            disk_open_inner(inner, true, layers)?;
1577        }
1578        DiskCliKind::PersistentReservationsWrapper(inner) => layers.push(disk(
1579            disk_backend_resources::DiskWithReservationsHandle(disk_open(inner, read_only)?),
1580        )),
1581        DiskCliKind::DelayDiskWrapper {
1582            delay_ms,
1583            disk: inner,
1584        } => layers.push(disk(DelayDiskHandle {
1585            delay: CellUpdater::new(Duration::from_millis(*delay_ms)).cell(),
1586            disk: disk_open(inner, read_only)?,
1587        })),
1588        DiskCliKind::Crypt {
1589            disk: inner,
1590            cipher,
1591            key_file,
1592        } => layers.push(disk(disk_crypt_resources::DiskCryptHandle {
1593            disk: disk_open(inner, read_only)?,
1594            cipher: match cipher {
1595                cli_args::DiskCipher::XtsAes256 => disk_crypt_resources::Cipher::XtsAes256,
1596            },
1597            key: fs_err::read(key_file).context("failed to read key file")?,
1598        })),
1599        DiskCliKind::Sqlite {
1600            path,
1601            create_with_len,
1602        } => {
1603            // FUTURE: this code should be responsible for opening
1604            // file-handle(s) itself, and passing them into sqlite via a custom
1605            // vfs. For now though - simply check if the file exists or not, and
1606            // perform early validation of filesystem-level create options.
1607            match (create_with_len.is_some(), path.exists()) {
1608                (true, true) => anyhow::bail!(
1609                    "cannot create new sqlite disk at {} - file already exists",
1610                    path.display()
1611                ),
1612                (false, false) => anyhow::bail!(
1613                    "cannot open sqlite disk at {} - file not found",
1614                    path.display()
1615                ),
1616                _ => {}
1617            }
1618
1619            layers.push(layer(SqliteDiskLayerHandle {
1620                dbhd_path: path.display().to_string(),
1621                format_dbhd: create_with_len.map(|len| {
1622                    disk_backend_resources::layer::SqliteDiskLayerFormatParams {
1623                        logically_read_only: false,
1624                        len: Some(len),
1625                    }
1626                }),
1627            }));
1628        }
1629        DiskCliKind::SqliteDiff { path, create, disk } => {
1630            // FUTURE: this code should be responsible for opening
1631            // file-handle(s) itself, and passing them into sqlite via a custom
1632            // vfs. For now though - simply check if the file exists or not, and
1633            // perform early validation of filesystem-level create options.
1634            match (create, path.exists()) {
1635                (true, true) => anyhow::bail!(
1636                    "cannot create new sqlite disk at {} - file already exists",
1637                    path.display()
1638                ),
1639                (false, false) => anyhow::bail!(
1640                    "cannot open sqlite disk at {} - file not found",
1641                    path.display()
1642                ),
1643                _ => {}
1644            }
1645
1646            layers.push(layer(SqliteDiskLayerHandle {
1647                dbhd_path: path.display().to_string(),
1648                format_dbhd: create.then_some(
1649                    disk_backend_resources::layer::SqliteDiskLayerFormatParams {
1650                        logically_read_only: false,
1651                        len: None,
1652                    },
1653                ),
1654            }));
1655            disk_open_inner(disk, true, layers)?;
1656        }
1657        DiskCliKind::AutoCacheSqlite {
1658            cache_path,
1659            key,
1660            disk,
1661        } => {
1662            layers.push(LayerOrDisk::Layer(DiskLayerDescription {
1663                read_cache: true,
1664                write_through: false,
1665                layer: SqliteAutoCacheDiskLayerHandle {
1666                    cache_path: cache_path.clone(),
1667                    cache_key: key.clone(),
1668                }
1669                .into_resource(),
1670            }));
1671            disk_open_inner(disk, read_only, layers)?;
1672        }
1673    }
1674    Ok(())
1675}
1676
1677fn do_main() -> anyhow::Result<()> {
1678    #[cfg(windows)]
1679    pal::windows::disable_hard_error_dialog();
1680
1681    tracing_init::enable_tracing()?;
1682
1683    // Try to run as a worker host.
1684    // On success the worker runs to completion and then exits the process (does
1685    // not return). Any worker host setup errors are return and bubbled up.
1686    meshworker::run_vmm_mesh_host()?;
1687
1688    let opt = Options::parse();
1689    if let Some(path) = &opt.write_saved_state_proto {
1690        mesh::payload::protofile::DescriptorWriter::new(vmcore::save_restore::saved_state_roots())
1691            .write_to_path(path)
1692            .context("failed to write protobuf descriptors")?;
1693        return Ok(());
1694    }
1695
1696    if let Some(path) = opt.relay_console_path {
1697        let console_title = opt.relay_console_title.unwrap_or_default();
1698        return console_relay::relay_console(&path, console_title.as_str());
1699    }
1700
1701    if let Some(path) = opt.ttrpc.as_ref().or(opt.grpc.as_ref()) {
1702        block_on(async {
1703            let _ = std::fs::remove_file(path);
1704            let listener =
1705                unix_socket::UnixListener::bind(path).context("failed to bind to socket")?;
1706
1707            let transport = if opt.ttrpc.is_some() {
1708                ttrpc::RpcTransport::Ttrpc
1709            } else {
1710                ttrpc::RpcTransport::Grpc
1711            };
1712
1713            // This is a local launch
1714            let mut handle = launch_local_worker::<TtrpcWorker>(ttrpc::Parameters {
1715                listener,
1716                transport,
1717            })
1718            .await?;
1719
1720            tracing::info!(%transport, path = %path.display(), "listening");
1721
1722            // Signal the the parent process that the server is ready.
1723            pal::close_stdout().context("failed to close stdout")?;
1724
1725            handle.join().await?;
1726
1727            Ok(())
1728        })
1729    } else {
1730        DefaultPool::run_with(async |driver| {
1731            let mesh = VmmMesh::new(&driver, opt.single_process)?;
1732            let result = run_control(&driver, &mesh, opt).await;
1733            mesh.shutdown().await;
1734            result
1735        })
1736    }
1737}
1738
1739fn maybe_with_radix_u64(s: &str) -> Result<u64, String> {
1740    let (radix, prefix_len) = if s.starts_with("0x") || s.starts_with("0X") {
1741        (16, 2)
1742    } else if s.starts_with("0o") || s.starts_with("0O") {
1743        (8, 2)
1744    } else if s.starts_with("0b") || s.starts_with("0B") {
1745        (2, 2)
1746    } else {
1747        (10, 0)
1748    };
1749
1750    u64::from_str_radix(&s[prefix_len..], radix).map_err(|e| format!("{e}"))
1751}
1752
1753#[derive(Parser)]
1754#[clap(
1755    name = "openvmm",
1756    disable_help_flag = true,
1757    disable_version_flag = true,
1758    no_binary_name = true,
1759    help_template("{subcommands}")
1760)]
1761enum InteractiveCommand {
1762    /// Restart the VM worker (experimental).
1763    ///
1764    /// This restarts the VM worker while preserving state.
1765    #[clap(visible_alias = "R")]
1766    Restart,
1767
1768    /// Inject an NMI.
1769    #[clap(visible_alias = "n")]
1770    Nmi,
1771
1772    /// Pause the VM.
1773    #[clap(visible_alias = "p")]
1774    Pause,
1775
1776    /// Resume the VM.
1777    #[clap(visible_alias = "r")]
1778    Resume,
1779
1780    /// Do a pulsed save restore (pause, save, reset, restore, resume) to the VM.
1781    #[clap(visible_alias = "psr")]
1782    PulseSaveRestore,
1783
1784    /// Schedule a pulsed save restore (pause, save, reset, restore, resume) to the VM.
1785    #[clap(visible_alias = "spsr")]
1786    SchedulePulseSaveRestore {
1787        /// The interval between pulse save restore operations in seconds.
1788        /// None or 0 means any previous scheduled pulse save restores will be cleared.
1789        interval: Option<u64>,
1790    },
1791
1792    /// Hot add a disk.
1793    #[clap(visible_alias = "d")]
1794    AddDisk {
1795        #[clap(long = "ro")]
1796        read_only: bool,
1797        #[clap(long = "dvd")]
1798        is_dvd: bool,
1799        #[clap(long, default_value_t)]
1800        target: u8,
1801        #[clap(long, default_value_t)]
1802        path: u8,
1803        #[clap(long, default_value_t)]
1804        lun: u8,
1805        #[clap(long)]
1806        ram: Option<u64>,
1807        file_path: Option<PathBuf>,
1808    },
1809
1810    /// Hot remove a disk.
1811    #[clap(visible_alias = "D")]
1812    RmDisk {
1813        #[clap(long)]
1814        target: u8,
1815        #[clap(long)]
1816        path: u8,
1817        #[clap(long)]
1818        lun: u8,
1819    },
1820
1821    /// Inspect program state.
1822    #[clap(visible_alias = "x")]
1823    Inspect {
1824        /// Enumerate state recursively.
1825        #[clap(short, long)]
1826        recursive: bool,
1827        /// The recursive depth limit.
1828        #[clap(short, long, requires("recursive"))]
1829        limit: Option<usize>,
1830        /// Target the paravisor.
1831        #[clap(short = 'v', long)]
1832        paravisor: bool,
1833        /// The element path to inspect.
1834        element: Option<String>,
1835        /// Update the path with a new value.
1836        #[clap(short, long, conflicts_with("recursive"))]
1837        update: Option<String>,
1838    },
1839
1840    /// Restart the VNC worker.
1841    #[clap(visible_alias = "V")]
1842    RestartVnc,
1843
1844    /// Start an hvsocket terminal window.
1845    #[clap(visible_alias = "v")]
1846    Hvsock {
1847        /// the terminal emulator to run (defaults to conhost.exe or xterm)
1848        #[clap(short, long)]
1849        term: Option<PathBuf>,
1850        /// the vsock port to connect to
1851        port: u32,
1852    },
1853
1854    /// Quit the program.
1855    #[clap(visible_alias = "q")]
1856    Quit,
1857
1858    /// Write input to the VM console.
1859    ///
1860    /// This will write each input parameter to the console's associated serial
1861    /// port, separated by spaces.
1862    #[clap(visible_alias = "i")]
1863    Input { data: Vec<String> },
1864
1865    /// Switch to input mode.
1866    ///
1867    /// Once in input mode, Ctrl-Q returns to command mode.
1868    #[clap(visible_alias = "I")]
1869    InputMode,
1870
1871    /// Reset the VM.
1872    Reset,
1873
1874    /// Send a request to the VM to shut it down.
1875    Shutdown {
1876        /// Reboot the VM instead of powering it off.
1877        #[clap(long, short = 'r')]
1878        reboot: bool,
1879        /// Hibernate the VM instead of powering it off.
1880        #[clap(long, short = 'h', conflicts_with = "reboot")]
1881        hibernate: bool,
1882        /// Tell the guest to force the power state transition.
1883        #[clap(long, short = 'f')]
1884        force: bool,
1885    },
1886
1887    /// Clears the current halt condition, resuming the VPs if the VM is
1888    /// running.
1889    #[clap(visible_alias = "ch")]
1890    ClearHalt,
1891
1892    /// Update the image in VTL2.
1893    ServiceVtl2 {
1894        /// Just restart the user-mode paravisor process, not the full
1895        /// firmware.
1896        #[clap(long, short = 'u')]
1897        user_mode_only: bool,
1898        /// The path to the new IGVM file. If missing, use the originally
1899        /// configured path.
1900        #[clap(long, conflicts_with("user_mode_only"))]
1901        igvm: Option<PathBuf>,
1902    },
1903
1904    /// Read guest memory
1905    ReadMemory {
1906        /// Guest physical address to start at.
1907        #[clap(value_parser=maybe_with_radix_u64)]
1908        gpa: u64,
1909        /// How many bytes to dump.
1910        #[clap(value_parser=maybe_with_radix_u64)]
1911        size: u64,
1912        /// File to save the data to. If omitted,
1913        /// the data will be presented as a hex dump.
1914        #[clap(long, short = 'f')]
1915        file: Option<PathBuf>,
1916    },
1917
1918    /// Write guest memory
1919    WriteMemory {
1920        /// Guest physical address to start at
1921        #[clap(value_parser=maybe_with_radix_u64)]
1922        gpa: u64,
1923        /// Hex string encoding data, with no `0x` radix.
1924        /// If omitted, the source file must be specified.
1925        hex: Option<String>,
1926        /// File to write the data from.
1927        #[clap(long, short = 'f')]
1928        file: Option<PathBuf>,
1929    },
1930
1931    /// Inject an artificial panic into OpenVMM
1932    Panic,
1933
1934    /// Use KVP to interact with the guest.
1935    Kvp(kvp::KvpCommand),
1936}
1937
1938struct CommandParser {
1939    app: clap::Command,
1940}
1941
1942impl CommandParser {
1943    fn new() -> Self {
1944        // Update the help template for each subcommand.
1945        let mut app = InteractiveCommand::command();
1946        for sc in app.get_subcommands_mut() {
1947            *sc = sc
1948                .clone()
1949                .help_template("{about-with-newline}\n{usage-heading}\n    {usage}\n\n{all-args}");
1950        }
1951        Self { app }
1952    }
1953
1954    fn parse(&mut self, line: &str) -> clap::error::Result<InteractiveCommand> {
1955        let args = shell_words::split(line)
1956            .map_err(|err| self.app.error(clap::error::ErrorKind::ValueValidation, err))?;
1957        let matches = self.app.try_get_matches_from_mut(args)?;
1958        InteractiveCommand::from_arg_matches(&matches).map_err(|err| err.format(&mut self.app))
1959    }
1960}
1961
1962fn new_hvsock_service_id(port: u32) -> Guid {
1963    // This GUID is an embedding of the AF_VSOCK port into an
1964    // AF_HYPERV service ID.
1965    Guid {
1966        data1: port,
1967        .."00000000-facb-11e6-bd58-64006a7986d3".parse().unwrap()
1968    }
1969}
1970
1971async fn run_control(driver: &DefaultDriver, mesh: &VmmMesh, opt: Options) -> anyhow::Result<()> {
1972    let (mut vm_config, mut resources) = vm_config_from_command_line(driver, &opt)?;
1973
1974    let mut vnc_worker = None;
1975    if opt.gfx || opt.vnc {
1976        let listener = TcpListener::bind(format!("127.0.0.1:{}", opt.vnc_port))
1977            .with_context(|| format!("binding to VNC port {}", opt.vnc_port))?;
1978
1979        let input_send = vm_config.input.sender();
1980        let framebuffer = resources.framebuffer_access.expect("synth video enabled");
1981
1982        let vnc_host = mesh
1983            .make_host("vnc", None)
1984            .await
1985            .context("spawning vnc process failed")?;
1986
1987        vnc_worker = Some(
1988            vnc_host
1989                .launch_worker(
1990                    vnc_worker_defs::VNC_WORKER_TCP,
1991                    VncParameters {
1992                        listener,
1993                        framebuffer,
1994                        input_send,
1995                    },
1996                )
1997                .await?,
1998        )
1999    }
2000
2001    // spin up the debug worker
2002    let gdb_worker = if let Some(port) = opt.gdb {
2003        let listener = TcpListener::bind(format!("127.0.0.1:{}", port))
2004            .with_context(|| format!("binding to gdb port {}", port))?;
2005
2006        let (req_tx, req_rx) = mesh::channel();
2007        vm_config.debugger_rpc = Some(req_rx);
2008
2009        let gdb_host = mesh
2010            .make_host("gdb", None)
2011            .await
2012            .context("spawning gdbstub process failed")?;
2013
2014        Some(
2015            gdb_host
2016                .launch_worker(
2017                    debug_worker_defs::DEBUGGER_WORKER,
2018                    debug_worker_defs::DebuggerParameters {
2019                        listener,
2020                        req_chan: req_tx,
2021                        vp_count: vm_config.processor_topology.proc_count,
2022                        target_arch: if cfg!(guest_arch = "x86_64") {
2023                            debug_worker_defs::TargetArch::X86_64
2024                        } else {
2025                            debug_worker_defs::TargetArch::Aarch64
2026                        },
2027                    },
2028                )
2029                .await
2030                .context("failed to launch gdbstub worker")?,
2031        )
2032    } else {
2033        None
2034    };
2035
2036    // spin up the VM
2037    let (vm_rpc, rpc_recv) = mesh::channel();
2038    let (notify_send, notify_recv) = mesh::channel();
2039    let mut vm_worker = {
2040        let vm_host = mesh.make_host("vm", opt.log_file.clone()).await?;
2041
2042        let params = VmWorkerParameters {
2043            hypervisor: opt.hypervisor,
2044            cfg: vm_config,
2045            saved_state: None,
2046            rpc: rpc_recv,
2047            notify: notify_send,
2048        };
2049        vm_host
2050            .launch_worker(VM_WORKER, params)
2051            .await
2052            .context("failed to launch vm worker")?
2053    };
2054
2055    if !opt.paused {
2056        vm_rpc.call(VmRpc::Resume, ()).await?;
2057    }
2058
2059    let paravisor_diag = Arc::new(diag_client::DiagClient::from_dialer(
2060        driver.clone(),
2061        DiagDialer {
2062            driver: driver.clone(),
2063            vm_rpc: vm_rpc.clone(),
2064            openhcl_vtl: if opt.vtl2 {
2065                DeviceVtl::Vtl2
2066            } else {
2067                DeviceVtl::Vtl0
2068            },
2069        },
2070    ));
2071
2072    let mut diag_inspector = DiagInspector::new(driver.clone(), paravisor_diag.clone());
2073
2074    let (console_command_send, console_command_recv) = mesh::channel();
2075    let (inspect_completion_engine_send, inspect_completion_engine_recv) = mesh::channel();
2076
2077    let mut console_in = resources.console_in;
2078    thread::Builder::new()
2079        .name("stdio-thread".to_string())
2080        .spawn(move || {
2081            // install panic hook to restore cooked terminal (linux)
2082            #[cfg(unix)]
2083            if io::stderr().is_terminal() {
2084                term::revert_terminal_on_panic()
2085            }
2086
2087            let mut rl = rustyline::Editor::<
2088                interactive_console::OpenvmmRustylineEditor,
2089                rustyline::history::FileHistory,
2090            >::with_config(
2091                rustyline::Config::builder()
2092                    .completion_type(rustyline::CompletionType::List)
2093                    .build(),
2094            )
2095            .unwrap();
2096
2097            rl.set_helper(Some(interactive_console::OpenvmmRustylineEditor {
2098                openvmm_inspect_req: Arc::new(inspect_completion_engine_send),
2099            }));
2100
2101            let history_file = {
2102                const HISTORY_FILE: &str = ".openvmm_history";
2103
2104                // using a `None` to kick off the `.or()` chain in order to make
2105                // it a bit easier to visually inspect the fallback chain.
2106                let history_folder = None
2107                    .or_else(dirs::state_dir)
2108                    .or_else(dirs::data_local_dir)
2109                    .map(|path| path.join("openvmm"));
2110
2111                if let Some(history_folder) = history_folder {
2112                    if let Err(err) = std::fs::create_dir_all(&history_folder) {
2113                        tracing::warn!(
2114                            error = &err as &dyn std::error::Error,
2115                            "could not create directory: {}",
2116                            history_folder.display()
2117                        )
2118                    }
2119
2120                    Some(history_folder.join(HISTORY_FILE))
2121                } else {
2122                    None
2123                }
2124            };
2125
2126            if let Some(history_file) = &history_file {
2127                tracing::info!("restoring history from {}", history_file.display());
2128                if rl.load_history(history_file).is_err() {
2129                    tracing::info!("could not find existing {}", history_file.display());
2130                }
2131            }
2132
2133            // Enable Ctrl-Backspace to delete the current word.
2134            rl.bind_sequence(
2135                rustyline::KeyEvent::new('\x08', rustyline::Modifiers::CTRL),
2136                rustyline::Cmd::Kill(rustyline::Movement::BackwardWord(1, rustyline::Word::Emacs)),
2137            );
2138
2139            let mut parser = CommandParser::new();
2140
2141            let mut stdin = io::stdin();
2142            loop {
2143                // Raw console text until Ctrl-Q.
2144                term::set_raw_console(true).expect("failed to set raw console mode");
2145
2146                if let Some(input) = console_in.as_mut() {
2147                    let mut buf = [0; 32];
2148                    loop {
2149                        let n = stdin.read(&mut buf).unwrap();
2150                        let mut b = &buf[..n];
2151                        let stop = if let Some(ctrlq) = b.iter().position(|x| *x == 0x11) {
2152                            b = &b[..ctrlq];
2153                            true
2154                        } else {
2155                            false
2156                        };
2157                        block_on(input.as_mut().write_all(b)).expect("BUGBUG");
2158                        if stop {
2159                            break;
2160                        }
2161                    }
2162                }
2163
2164                term::set_raw_console(false).expect("failed to set raw console mode");
2165
2166                loop {
2167                    let line = rl.readline("openvmm> ");
2168                    if line.is_err() {
2169                        break;
2170                    }
2171                    let line = line.unwrap();
2172                    let trimmed = line.trim();
2173                    if trimmed.is_empty() {
2174                        continue;
2175                    }
2176                    if let Err(err) = rl.add_history_entry(&line) {
2177                        tracing::warn!(
2178                            err = &err as &dyn std::error::Error,
2179                            "error adding to .openvmm_history"
2180                        )
2181                    }
2182
2183                    match parser.parse(trimmed) {
2184                        Ok(cmd) => match cmd {
2185                            InteractiveCommand::Input { data } => {
2186                                let mut data = data.join(" ");
2187                                data.push('\n');
2188                                if let Some(input) = console_in.as_mut() {
2189                                    block_on(input.write_all(data.as_bytes())).expect("BUGBUG");
2190                                }
2191                            }
2192                            InteractiveCommand::InputMode => break,
2193                            cmd => {
2194                                // Send the command to the main thread for processing.
2195                                let (processing_done_send, processing_done_recv) =
2196                                    mesh::oneshot::<()>();
2197                                console_command_send.send((cmd, processing_done_send));
2198                                let _ = block_on(processing_done_recv);
2199                            }
2200                        },
2201                        Err(err) => {
2202                            err.print().unwrap();
2203                        }
2204                    }
2205
2206                    if let Some(history_file) = &history_file {
2207                        rl.append_history(history_file).unwrap();
2208                    }
2209                }
2210            }
2211        })
2212        .unwrap();
2213
2214    let mut state_change_task = None::<Task<Result<StateChange, RpcError>>>;
2215    let mut pulse_save_restore_interval: Option<Duration> = None;
2216    let mut pending_shutdown = None;
2217
2218    enum StateChange {
2219        Pause(bool),
2220        Resume(bool),
2221        Reset(Result<(), RemoteError>),
2222        PulseSaveRestore(Result<(), PulseSaveRestoreError>),
2223        ServiceVtl2(anyhow::Result<Duration>),
2224    }
2225
2226    enum Event {
2227        Command((InteractiveCommand, mesh::OneshotSender<()>)),
2228        InspectRequestFromCompletionEngine(
2229            (InspectTarget, String, mesh::OneshotSender<inspect::Node>),
2230        ),
2231        Quit,
2232        Halt(vmm_core_defs::HaltReason),
2233        PulseSaveRestore,
2234        Worker(WorkerEvent),
2235        VncWorker(WorkerEvent),
2236        StateChange(Result<StateChange, RpcError>),
2237        ShutdownResult(Result<hyperv_ic_resources::shutdown::ShutdownResult, RpcError>),
2238    }
2239
2240    let mut console_command_recv = console_command_recv
2241        .map(Event::Command)
2242        .chain(futures::stream::repeat_with(|| Event::Quit));
2243
2244    let mut notify_recv = notify_recv.map(Event::Halt);
2245
2246    let mut inspect_completion_engine_recv =
2247        inspect_completion_engine_recv.map(Event::InspectRequestFromCompletionEngine);
2248
2249    let mut quit = false;
2250    loop {
2251        let event = {
2252            let pulse_save_restore = pin!(async {
2253                match pulse_save_restore_interval {
2254                    Some(wait) => {
2255                        PolledTimer::new(driver).sleep(wait).await;
2256                        Event::PulseSaveRestore
2257                    }
2258                    None => pending().await,
2259                }
2260            });
2261
2262            let vm = (&mut vm_worker).map(Event::Worker);
2263            let vnc = futures::stream::iter(vnc_worker.as_mut())
2264                .flatten()
2265                .map(Event::VncWorker);
2266            let change = futures::stream::iter(state_change_task.as_mut().map(|x| x.into_stream()))
2267                .flatten()
2268                .map(Event::StateChange);
2269            let shutdown = pin!(async {
2270                if let Some(s) = &mut pending_shutdown {
2271                    Event::ShutdownResult(s.await)
2272                } else {
2273                    pending().await
2274                }
2275            });
2276
2277            (
2278                &mut console_command_recv,
2279                &mut inspect_completion_engine_recv,
2280                &mut notify_recv,
2281                pulse_save_restore.into_stream(),
2282                vm,
2283                vnc,
2284                change,
2285                shutdown.into_stream(),
2286            )
2287                .merge()
2288                .next()
2289                .await
2290                .unwrap()
2291        };
2292
2293        let (cmd, _processing_done_send) = match event {
2294            Event::Command(message) => message,
2295            Event::InspectRequestFromCompletionEngine((vtl, path, res)) => {
2296                let mut inspection =
2297                    InspectionBuilder::new(&path)
2298                        .depth(Some(1))
2299                        .inspect(inspect_obj(
2300                            vtl,
2301                            mesh,
2302                            &vm_worker,
2303                            vnc_worker.as_ref(),
2304                            gdb_worker.as_ref(),
2305                            &mut diag_inspector,
2306                        ));
2307                let _ = CancelContext::new()
2308                    .with_timeout(Duration::from_secs(1))
2309                    .until_cancelled(inspection.resolve())
2310                    .await;
2311
2312                let node = inspection.results();
2313                res.send(node);
2314                continue;
2315            }
2316            Event::Quit => break,
2317            Event::Halt(reason) => {
2318                tracing::info!(?reason, "guest halted");
2319                continue;
2320            }
2321            Event::PulseSaveRestore => {
2322                vm_rpc.call(VmRpc::PulseSaveRestore, ()).await??;
2323                continue;
2324            }
2325            Event::Worker(event) => {
2326                match event {
2327                    WorkerEvent::Stopped => {
2328                        if quit {
2329                            tracing::info!("vm stopped");
2330                        } else {
2331                            tracing::error!("vm worker unexpectedly stopped");
2332                        }
2333                        break;
2334                    }
2335                    WorkerEvent::Failed(err) => {
2336                        tracing::error!(error = &err as &dyn std::error::Error, "vm worker failed");
2337                        break;
2338                    }
2339                    WorkerEvent::RestartFailed(err) => {
2340                        tracing::error!(
2341                            error = &err as &dyn std::error::Error,
2342                            "vm worker restart failed"
2343                        );
2344                    }
2345                    WorkerEvent::Started => {
2346                        tracing::info!("vm worker restarted");
2347                    }
2348                }
2349                continue;
2350            }
2351            Event::VncWorker(event) => {
2352                match event {
2353                    WorkerEvent::Stopped => tracing::error!("vnc unexpectedly stopped"),
2354                    WorkerEvent::Failed(err) => {
2355                        tracing::error!(
2356                            error = &err as &dyn std::error::Error,
2357                            "vnc worker failed"
2358                        );
2359                    }
2360                    WorkerEvent::RestartFailed(err) => {
2361                        tracing::error!(
2362                            error = &err as &dyn std::error::Error,
2363                            "vnc worker restart failed"
2364                        );
2365                    }
2366                    WorkerEvent::Started => {
2367                        tracing::info!("vnc worker restarted");
2368                    }
2369                }
2370                continue;
2371            }
2372            Event::StateChange(r) => {
2373                match r {
2374                    Ok(sc) => match sc {
2375                        StateChange::Pause(success) => {
2376                            if success {
2377                                tracing::info!("pause complete");
2378                            } else {
2379                                tracing::warn!("already paused");
2380                            }
2381                        }
2382                        StateChange::Resume(success) => {
2383                            if success {
2384                                tracing::info!("resumed complete");
2385                            } else {
2386                                tracing::warn!("already running");
2387                            }
2388                        }
2389                        StateChange::Reset(r) => match r {
2390                            Ok(()) => tracing::info!("reset complete"),
2391                            Err(err) => tracing::error!(
2392                                error = &err as &dyn std::error::Error,
2393                                "reset failed"
2394                            ),
2395                        },
2396                        StateChange::PulseSaveRestore(r) => match r {
2397                            Ok(()) => tracing::info!("pulse save/restore complete"),
2398                            Err(err) => tracing::error!(
2399                                error = &err as &dyn std::error::Error,
2400                                "pulse save/restore failed"
2401                            ),
2402                        },
2403                        StateChange::ServiceVtl2(r) => match r {
2404                            Ok(dur) => {
2405                                tracing::info!(
2406                                    duration = dur.as_millis() as i64,
2407                                    "vtl2 servicing complete"
2408                                )
2409                            }
2410                            Err(err) => tracing::error!(
2411                                error = err.as_ref() as &dyn std::error::Error,
2412                                "vtl2 servicing failed"
2413                            ),
2414                        },
2415                    },
2416                    Err(err) => {
2417                        tracing::error!(
2418                            error = &err as &dyn std::error::Error,
2419                            "communication failure during state change"
2420                        );
2421                    }
2422                }
2423                state_change_task = None;
2424                continue;
2425            }
2426            Event::ShutdownResult(r) => {
2427                match r {
2428                    Ok(r) => match r {
2429                        hyperv_ic_resources::shutdown::ShutdownResult::Ok => {
2430                            tracing::info!("shutdown initiated");
2431                        }
2432                        hyperv_ic_resources::shutdown::ShutdownResult::NotReady => {
2433                            tracing::error!("shutdown ic not ready");
2434                        }
2435                        hyperv_ic_resources::shutdown::ShutdownResult::AlreadyInProgress => {
2436                            tracing::error!("shutdown already in progress");
2437                        }
2438                        hyperv_ic_resources::shutdown::ShutdownResult::Failed(hr) => {
2439                            tracing::error!("shutdown failed with error code {hr:#x}");
2440                        }
2441                    },
2442                    Err(err) => {
2443                        tracing::error!(
2444                            error = &err as &dyn std::error::Error,
2445                            "communication failure during shutdown"
2446                        );
2447                    }
2448                }
2449                pending_shutdown = None;
2450                continue;
2451            }
2452        };
2453
2454        fn inspect_obj<'a>(
2455            target: InspectTarget,
2456            mesh: &'a VmmMesh,
2457            vm_worker: &'a WorkerHandle,
2458            vnc_worker: Option<&'a WorkerHandle>,
2459            gdb_worker: Option<&'a WorkerHandle>,
2460            diag_inspector: &'a mut DiagInspector,
2461        ) -> impl 'a + InspectMut {
2462            inspect::adhoc_mut(move |req| match target {
2463                InspectTarget::Host => {
2464                    let mut resp = req.respond();
2465                    resp.field("mesh", mesh)
2466                        .field("vm", vm_worker)
2467                        .field("vnc", vnc_worker)
2468                        .field("gdb", gdb_worker);
2469                }
2470                InspectTarget::Paravisor => {
2471                    diag_inspector.inspect_mut(req);
2472                }
2473            })
2474        }
2475
2476        fn state_change<U: 'static + Send>(
2477            driver: impl Spawn,
2478            vm_rpc: &mesh::Sender<VmRpc>,
2479            state_change_task: &mut Option<Task<Result<StateChange, RpcError>>>,
2480            f: impl FnOnce(Rpc<(), U>) -> VmRpc,
2481            g: impl FnOnce(U) -> StateChange + 'static + Send,
2482        ) {
2483            if state_change_task.is_some() {
2484                tracing::error!("state change already in progress");
2485            } else {
2486                let rpc = vm_rpc.call(f, ());
2487                *state_change_task =
2488                    Some(driver.spawn("state-change", async move { Ok(g(rpc.await?)) }));
2489            }
2490        }
2491
2492        match cmd {
2493            InteractiveCommand::Panic => {
2494                panic!("injected panic")
2495            }
2496            InteractiveCommand::Restart => {
2497                // create a new host process
2498                let vm_host = mesh.make_host("vm", opt.log_file.clone()).await?;
2499
2500                vm_worker.restart(&vm_host);
2501            }
2502            InteractiveCommand::Pause => {
2503                state_change(
2504                    driver,
2505                    &vm_rpc,
2506                    &mut state_change_task,
2507                    VmRpc::Pause,
2508                    StateChange::Pause,
2509                );
2510            }
2511            InteractiveCommand::Resume => {
2512                state_change(
2513                    driver,
2514                    &vm_rpc,
2515                    &mut state_change_task,
2516                    VmRpc::Resume,
2517                    StateChange::Resume,
2518                );
2519            }
2520            InteractiveCommand::Reset => {
2521                state_change(
2522                    driver,
2523                    &vm_rpc,
2524                    &mut state_change_task,
2525                    VmRpc::Reset,
2526                    StateChange::Reset,
2527                );
2528            }
2529            InteractiveCommand::PulseSaveRestore => {
2530                state_change(
2531                    driver,
2532                    &vm_rpc,
2533                    &mut state_change_task,
2534                    VmRpc::PulseSaveRestore,
2535                    StateChange::PulseSaveRestore,
2536                );
2537            }
2538            InteractiveCommand::SchedulePulseSaveRestore { interval } => {
2539                pulse_save_restore_interval = match interval {
2540                    Some(seconds) if seconds != 0 => Some(Duration::from_secs(seconds)),
2541                    _ => {
2542                        // Treat None and 0 seconds as do not perform scheduled pulse save restores anymore.
2543                        None
2544                    }
2545                }
2546            }
2547            InteractiveCommand::Shutdown {
2548                reboot,
2549                hibernate,
2550                force,
2551            } => {
2552                if pending_shutdown.is_some() {
2553                    println!("shutdown already in progress");
2554                } else if let Some(ic) = &resources.shutdown_ic {
2555                    let params = hyperv_ic_resources::shutdown::ShutdownParams {
2556                        shutdown_type: if hibernate {
2557                            hyperv_ic_resources::shutdown::ShutdownType::Hibernate
2558                        } else if reboot {
2559                            hyperv_ic_resources::shutdown::ShutdownType::Reboot
2560                        } else {
2561                            hyperv_ic_resources::shutdown::ShutdownType::PowerOff
2562                        },
2563                        force,
2564                    };
2565                    pending_shutdown =
2566                        Some(ic.call(hyperv_ic_resources::shutdown::ShutdownRpc::Shutdown, params));
2567                } else {
2568                    println!("no shutdown ic configured");
2569                }
2570            }
2571            InteractiveCommand::Nmi => {
2572                let _ = vm_rpc.call(VmRpc::Nmi, 0).await;
2573            }
2574            InteractiveCommand::ClearHalt => {
2575                vm_rpc.call(VmRpc::ClearHalt, ()).await.ok();
2576            }
2577            InteractiveCommand::AddDisk {
2578                read_only,
2579                target,
2580                path,
2581                lun,
2582                ram,
2583                file_path,
2584                is_dvd,
2585            } => {
2586                let action = async {
2587                    let scsi = resources.scsi_rpc.as_ref().context("no scsi controller")?;
2588                    let disk_type = match ram {
2589                        None => {
2590                            let path = file_path.context("no filename passed")?;
2591                            open_disk_type(path.as_ref(), read_only)
2592                                .with_context(|| format!("failed to open {}", path.display()))?
2593                        }
2594                        Some(size) => {
2595                            Resource::new(disk_backend_resources::LayeredDiskHandle::single_layer(
2596                                RamDiskLayerHandle { len: Some(size) },
2597                            ))
2598                        }
2599                    };
2600
2601                    let device = if is_dvd {
2602                        SimpleScsiDvdHandle {
2603                            media: Some(disk_type),
2604                            requests: None,
2605                        }
2606                        .into_resource()
2607                    } else {
2608                        SimpleScsiDiskHandle {
2609                            disk: disk_type,
2610                            read_only,
2611                            parameters: Default::default(),
2612                        }
2613                        .into_resource()
2614                    };
2615
2616                    let cfg = ScsiDeviceAndPath {
2617                        path: ScsiPath { path, target, lun },
2618                        device,
2619                    };
2620
2621                    scsi.call_failable(ScsiControllerRequest::AddDevice, cfg)
2622                        .await?;
2623
2624                    anyhow::Result::<_>::Ok(())
2625                };
2626
2627                if let Err(error) = action.await {
2628                    tracing::error!(error = error.as_error(), "error adding disk")
2629                }
2630            }
2631            InteractiveCommand::RmDisk { target, path, lun } => {
2632                let action = async {
2633                    let scsi = resources.scsi_rpc.as_ref().context("no scsi controller")?;
2634                    scsi.call_failable(
2635                        ScsiControllerRequest::RemoveDevice,
2636                        ScsiPath { target, path, lun },
2637                    )
2638                    .await?;
2639                    anyhow::Ok(())
2640                };
2641
2642                if let Err(error) = action.await {
2643                    tracing::error!(error = error.as_error(), "error removing disk")
2644                }
2645            }
2646            InteractiveCommand::Inspect {
2647                recursive,
2648                limit,
2649                paravisor,
2650                element,
2651                update,
2652            } => {
2653                let obj = inspect_obj(
2654                    if paravisor {
2655                        InspectTarget::Paravisor
2656                    } else {
2657                        InspectTarget::Host
2658                    },
2659                    mesh,
2660                    &vm_worker,
2661                    vnc_worker.as_ref(),
2662                    gdb_worker.as_ref(),
2663                    &mut diag_inspector,
2664                );
2665
2666                if let Some(value) = update {
2667                    let Some(element) = element else {
2668                        anyhow::bail!("must provide element for update")
2669                    };
2670
2671                    let value = async {
2672                        let update = inspect::update(&element, &value, obj);
2673                        let value = CancelContext::new()
2674                            .with_timeout(Duration::from_secs(1))
2675                            .until_cancelled(update)
2676                            .await??;
2677                        anyhow::Ok(value)
2678                    }
2679                    .await;
2680                    match value {
2681                        Ok(node) => println!("{:#}", node),
2682                        Err(err) => println!("error: {:#}", err),
2683                    }
2684                } else {
2685                    let element = element.unwrap_or_default();
2686                    let depth = if recursive { limit } else { Some(0) };
2687                    let node = async {
2688                        let mut inspection =
2689                            InspectionBuilder::new(&element).depth(depth).inspect(obj);
2690                        let _ = CancelContext::new()
2691                            .with_timeout(Duration::from_secs(1))
2692                            .until_cancelled(inspection.resolve())
2693                            .await;
2694                        inspection.results()
2695                    }
2696                    .await;
2697
2698                    println!("{:#}", node);
2699                }
2700            }
2701            InteractiveCommand::RestartVnc => {
2702                if let Some(vnc) = &mut vnc_worker {
2703                    let action = async {
2704                        let vnc_host = mesh
2705                            .make_host("vnc", None)
2706                            .await
2707                            .context("spawning vnc process failed")?;
2708
2709                        vnc.restart(&vnc_host);
2710                        anyhow::Result::<_>::Ok(())
2711                    };
2712
2713                    if let Err(error) = action.await {
2714                        eprintln!("error: {}", error);
2715                    }
2716                } else {
2717                    eprintln!("ERROR: no VNC server running");
2718                }
2719            }
2720            InteractiveCommand::Hvsock { term, port } => {
2721                let vm_rpc = &vm_rpc;
2722                let action = async || {
2723                    let service_id = new_hvsock_service_id(port);
2724                    let socket = vm_rpc
2725                        .call_failable(
2726                            VmRpc::ConnectHvsock,
2727                            (
2728                                CancelContext::new().with_timeout(Duration::from_secs(2)),
2729                                service_id,
2730                                DeviceVtl::Vtl0,
2731                            ),
2732                        )
2733                        .await?;
2734                    let socket = PolledSocket::new(driver, socket)?;
2735                    let mut console = console_relay::Console::new(
2736                        driver.clone(),
2737                        term.or_else(openvmm_terminal_app).as_deref(),
2738                        Some(ConsoleLaunchOptions {
2739                            window_title: Some(format!("HVSock{} [OpenVMM]", port)),
2740                        }),
2741                    )?;
2742                    driver
2743                        .spawn("console-relay", async move { console.relay(socket).await })
2744                        .detach();
2745                    anyhow::Result::<_>::Ok(())
2746                };
2747
2748                if let Err(error) = (action)().await {
2749                    eprintln!("error: {}", error);
2750                }
2751            }
2752            InteractiveCommand::ServiceVtl2 {
2753                user_mode_only,
2754                igvm,
2755            } => {
2756                let paravisor_diag = paravisor_diag.clone();
2757                let vm_rpc = vm_rpc.clone();
2758                let igvm = igvm.or_else(|| opt.igvm.clone());
2759                let ged_rpc = resources.ged_rpc.clone();
2760                let r = async move {
2761                    let start;
2762                    if user_mode_only {
2763                        start = Instant::now();
2764                        paravisor_diag.restart().await?;
2765                    } else {
2766                        let path = igvm.context("no igvm file loaded")?;
2767                        let file = fs_err::File::open(path)?;
2768                        start = Instant::now();
2769                        hvlite_helpers::underhill::service_underhill(
2770                            &vm_rpc,
2771                            ged_rpc.as_ref().context("no GED")?,
2772                            GuestServicingFlags::default(),
2773                            file.into(),
2774                        )
2775                        .await?;
2776                    }
2777                    let end = Instant::now();
2778                    Ok(end - start)
2779                }
2780                .map(|r| Ok(StateChange::ServiceVtl2(r)));
2781                if state_change_task.is_some() {
2782                    tracing::error!("state change already in progress");
2783                } else {
2784                    state_change_task = Some(driver.spawn("state-change", r));
2785                }
2786            }
2787            InteractiveCommand::Quit => {
2788                tracing::info!("quitting");
2789                // Work around the detached SCSI task holding up worker stop.
2790                // TODO: Fix the underlying bug
2791                resources.scsi_rpc = None;
2792
2793                vm_worker.stop();
2794                quit = true;
2795            }
2796            InteractiveCommand::ReadMemory { gpa, size, file } => {
2797                let size = size as usize;
2798                let data = vm_rpc.call(VmRpc::ReadMemory, (gpa, size)).await?;
2799
2800                match data {
2801                    Ok(bytes) => {
2802                        if let Some(file) = file {
2803                            if let Err(err) = fs_err::write(file, bytes) {
2804                                eprintln!("error: {err:?}");
2805                            }
2806                        } else {
2807                            let width = 16;
2808                            let show_ascii = true;
2809
2810                            let mut dump = String::new();
2811                            for (i, chunk) in bytes.chunks(width).enumerate() {
2812                                let hex_part: Vec<String> =
2813                                    chunk.iter().map(|byte| format!("{:02x}", byte)).collect();
2814                                let hex_line = hex_part.join(" ");
2815
2816                                if show_ascii {
2817                                    let ascii_part: String = chunk
2818                                        .iter()
2819                                        .map(|&byte| {
2820                                            if byte.is_ascii_graphic() || byte == b' ' {
2821                                                byte as char
2822                                            } else {
2823                                                '.'
2824                                            }
2825                                        })
2826                                        .collect();
2827                                    dump.push_str(&format!(
2828                                        "{:04x}: {:<width$}  {}\n",
2829                                        i * width,
2830                                        hex_line,
2831                                        ascii_part,
2832                                        width = width * 3 - 1
2833                                    ));
2834                                } else {
2835                                    dump.push_str(&format!("{:04x}: {}\n", i * width, hex_line));
2836                                }
2837                            }
2838
2839                            println!("{dump}");
2840                        }
2841                    }
2842                    Err(err) => {
2843                        eprintln!("error: {err:?}");
2844                    }
2845                }
2846            }
2847            InteractiveCommand::WriteMemory { gpa, hex, file } => {
2848                if hex.is_some() == file.is_some() {
2849                    eprintln!("error: either path to the file or the hex string must be specified");
2850                    continue;
2851                }
2852
2853                let data = if let Some(file) = file {
2854                    let data = fs_err::read(file);
2855                    match data {
2856                        Ok(data) => data,
2857                        Err(err) => {
2858                            eprintln!("error: {err:?}");
2859                            continue;
2860                        }
2861                    }
2862                } else if let Some(hex) = hex {
2863                    if hex.len() & 1 != 0 {
2864                        eprintln!(
2865                            "error: expected even number of hex digits (2 hex digits per byte)"
2866                        );
2867                        continue;
2868                    }
2869                    let data: Result<Vec<u8>, String> = (0..hex.len())
2870                        .step_by(2)
2871                        .map(|i| {
2872                            u8::from_str_radix(&hex[i..i + 2], 16).map_err(|e| {
2873                                format!("invalid hex character at position {}: {}", i, e)
2874                            })
2875                        })
2876                        .collect();
2877
2878                    match data {
2879                        Ok(data) => data,
2880                        Err(err) => {
2881                            eprintln!("error: {err}");
2882                            continue;
2883                        }
2884                    }
2885                } else {
2886                    unreachable!();
2887                };
2888
2889                if data.is_empty() {
2890                    eprintln!("error: no data to write");
2891                    continue;
2892                }
2893
2894                if let Err(err) = vm_rpc.call(VmRpc::WriteMemory, (gpa, data)).await? {
2895                    eprintln!("error: {err:?}");
2896                }
2897            }
2898            InteractiveCommand::Kvp(command) => {
2899                let Some(kvp) = &resources.kvp_ic else {
2900                    eprintln!("error: no kvp ic configured");
2901                    continue;
2902                };
2903                if let Err(err) = kvp::handle_kvp(kvp, command).await {
2904                    eprintln!("error: {err:#}");
2905                }
2906            }
2907            InteractiveCommand::Input { .. } | InteractiveCommand::InputMode => unreachable!(),
2908        }
2909    }
2910
2911    vm_worker.stop();
2912    vm_worker.join().await?;
2913    Ok(())
2914}
2915
2916struct DiagDialer {
2917    driver: DefaultDriver,
2918    vm_rpc: mesh::Sender<VmRpc>,
2919    openhcl_vtl: DeviceVtl,
2920}
2921
2922impl mesh_rpc::client::Dial for DiagDialer {
2923    type Stream = PolledSocket<unix_socket::UnixStream>;
2924
2925    async fn dial(&mut self) -> io::Result<Self::Stream> {
2926        let service_id = new_hvsock_service_id(1);
2927        let socket = self
2928            .vm_rpc
2929            .call_failable(
2930                VmRpc::ConnectHvsock,
2931                (
2932                    CancelContext::new().with_timeout(Duration::from_secs(2)),
2933                    service_id,
2934                    self.openhcl_vtl,
2935                ),
2936            )
2937            .await
2938            .map_err(io::Error::other)?;
2939
2940        PolledSocket::new(&self.driver, socket)
2941    }
2942}
2943
2944/// An object that implements [`InspectMut`] by sending an inspect request over
2945/// TTRPC to the guest (typically the paravisor running in VTL2), then stitching
2946/// the response back into the inspect tree.
2947///
2948/// This also caches the TTRPC connection to the guest so that only the first
2949/// inspect request has to wait for the connection to be established.
2950pub struct DiagInspector(DiagInspectorInner);
2951
2952enum DiagInspectorInner {
2953    NotStarted(DefaultDriver, Arc<diag_client::DiagClient>),
2954    Started {
2955        send: mesh::Sender<inspect::Deferred>,
2956        _task: Task<()>,
2957    },
2958    Invalid,
2959}
2960
2961impl DiagInspector {
2962    pub fn new(driver: DefaultDriver, diag_client: Arc<diag_client::DiagClient>) -> Self {
2963        Self(DiagInspectorInner::NotStarted(driver, diag_client))
2964    }
2965
2966    fn start(&mut self) -> &mesh::Sender<inspect::Deferred> {
2967        loop {
2968            match self.0 {
2969                DiagInspectorInner::NotStarted { .. } => {
2970                    let DiagInspectorInner::NotStarted(driver, client) =
2971                        std::mem::replace(&mut self.0, DiagInspectorInner::Invalid)
2972                    else {
2973                        unreachable!()
2974                    };
2975                    let (send, recv) = mesh::channel();
2976                    let task = driver.clone().spawn("diag-inspect", async move {
2977                        Self::run(&client, recv).await
2978                    });
2979
2980                    self.0 = DiagInspectorInner::Started { send, _task: task };
2981                }
2982                DiagInspectorInner::Started { ref send, .. } => break send,
2983                DiagInspectorInner::Invalid => unreachable!(),
2984            }
2985        }
2986    }
2987
2988    async fn run(
2989        diag_client: &diag_client::DiagClient,
2990        mut recv: mesh::Receiver<inspect::Deferred>,
2991    ) {
2992        while let Some(deferred) = recv.next().await {
2993            let info = deferred.external_request();
2994            let result = match info.request_type {
2995                inspect::ExternalRequestType::Inspect { depth } => {
2996                    if depth == 0 {
2997                        Ok(inspect::Node::Unevaluated)
2998                    } else {
2999                        // TODO: Support taking timeouts from the command line
3000                        diag_client
3001                            .inspect(info.path, Some(depth - 1), Some(Duration::from_secs(1)))
3002                            .await
3003                    }
3004                }
3005                inspect::ExternalRequestType::Update { value } => {
3006                    (diag_client.update(info.path, value).await).map(inspect::Node::Value)
3007                }
3008            };
3009            deferred.complete_external(
3010                result.unwrap_or_else(|err| {
3011                    inspect::Node::Failed(inspect::Error::Mesh(format!("{err:#}")))
3012                }),
3013                inspect::SensitivityLevel::Unspecified,
3014            )
3015        }
3016    }
3017}
3018
3019impl InspectMut for DiagInspector {
3020    fn inspect_mut(&mut self, req: inspect::Request<'_>) {
3021        self.start().send(req.defer());
3022    }
3023}
3024
3025enum InspectTarget {
3026    Host,
3027    Paravisor,
3028}
3029
3030mod interactive_console {
3031    use super::InteractiveCommand;
3032    use rustyline::Helper;
3033    use rustyline::Highlighter;
3034    use rustyline::Hinter;
3035    use rustyline::Validator;
3036
3037    #[derive(Helper, Highlighter, Hinter, Validator)]
3038    pub(crate) struct OpenvmmRustylineEditor {
3039        pub openvmm_inspect_req: std::sync::Arc<
3040            mesh::Sender<(
3041                super::InspectTarget,
3042                String,
3043                mesh::OneshotSender<inspect::Node>,
3044            )>,
3045        >,
3046    }
3047
3048    impl rustyline::completion::Completer for OpenvmmRustylineEditor {
3049        type Candidate = String;
3050
3051        fn complete(
3052            &self,
3053            line: &str,
3054            pos: usize,
3055            _ctx: &rustyline::Context<'_>,
3056        ) -> rustyline::Result<(usize, Vec<Self::Candidate>)> {
3057            let Ok(cmd) = shell_words::split(line) else {
3058                return Ok((0, Vec::with_capacity(0)));
3059            };
3060
3061            let completions = futures::executor::block_on(
3062                clap_dyn_complete::Complete {
3063                    cmd,
3064                    raw: Some(line.into()),
3065                    position: Some(pos),
3066                }
3067                .generate_completions::<InteractiveCommand>(None, self),
3068            );
3069
3070            let pos_from_end = {
3071                let line = line.chars().take(pos).collect::<String>();
3072
3073                let trailing_ws = line.len() - line.trim_end().len();
3074
3075                if trailing_ws > 0 {
3076                    line.len() - trailing_ws + 1 // +1 for the space
3077                } else {
3078                    let last_word = shell_words::split(&line)
3079                        .unwrap_or_default()
3080                        .last()
3081                        .cloned()
3082                        .unwrap_or_default();
3083
3084                    line.len() - last_word.len()
3085                }
3086            };
3087
3088            Ok((pos_from_end, completions))
3089        }
3090    }
3091
3092    impl clap_dyn_complete::CustomCompleterFactory for &OpenvmmRustylineEditor {
3093        type CustomCompleter = OpenvmmComplete;
3094        async fn build(&self, _ctx: &clap_dyn_complete::RootCtx<'_>) -> Self::CustomCompleter {
3095            OpenvmmComplete {
3096                openvmm_inspect_req: self.openvmm_inspect_req.clone(),
3097            }
3098        }
3099    }
3100
3101    pub struct OpenvmmComplete {
3102        openvmm_inspect_req: std::sync::Arc<
3103            mesh::Sender<(
3104                super::InspectTarget,
3105                String,
3106                mesh::OneshotSender<inspect::Node>,
3107            )>,
3108        >,
3109    }
3110
3111    impl clap_dyn_complete::CustomCompleter for OpenvmmComplete {
3112        async fn complete(
3113            &self,
3114            ctx: &clap_dyn_complete::RootCtx<'_>,
3115            subcommand_path: &[&str],
3116            arg_id: &str,
3117        ) -> Vec<String> {
3118            match (subcommand_path, arg_id) {
3119                (["openvmm", "inspect"], "element") => {
3120                    let on_error = vec!["failed/to/connect".into()];
3121
3122                    let (parent_path, to_complete) = (ctx.to_complete)
3123                        .rsplit_once('/')
3124                        .unwrap_or(("", ctx.to_complete));
3125
3126                    let node = {
3127                        let paravisor = {
3128                            let raw_arg = ctx
3129                                .matches
3130                                .subcommand()
3131                                .unwrap()
3132                                .1
3133                                .get_one::<String>("paravisor")
3134                                .map(|x| x.as_str())
3135                                .unwrap_or_default();
3136                            raw_arg == "true"
3137                        };
3138
3139                        let (tx, rx) = mesh::oneshot();
3140                        self.openvmm_inspect_req.send((
3141                            if paravisor {
3142                                super::InspectTarget::Paravisor
3143                            } else {
3144                                super::InspectTarget::Host
3145                            },
3146                            parent_path.to_owned(),
3147                            tx,
3148                        ));
3149                        let Ok(node) = rx.await else {
3150                            return on_error;
3151                        };
3152
3153                        node
3154                    };
3155
3156                    let mut completions = Vec::new();
3157
3158                    if let inspect::Node::Dir(dir) = node {
3159                        for entry in dir {
3160                            if entry.name.starts_with(to_complete) {
3161                                if parent_path.is_empty() {
3162                                    completions.push(format!("{}/", entry.name))
3163                                } else {
3164                                    completions.push(format!(
3165                                        "{}/{}{}",
3166                                        parent_path,
3167                                        entry.name,
3168                                        if matches!(entry.node, inspect::Node::Dir(..)) {
3169                                            "/"
3170                                        } else {
3171                                            ""
3172                                        }
3173                                    ))
3174                                }
3175                            }
3176                        }
3177                    } else {
3178                        return on_error;
3179                    }
3180
3181                    completions
3182                }
3183                _ => Vec::new(),
3184            }
3185        }
3186    }
3187}