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