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