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