crosvm/
main.rs

1// Copyright 2017 The ChromiumOS Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Runs a virtual machine
6//!
7//! ## Feature flags
8#![cfg_attr(feature = "document-features", doc = document_features::document_features!())]
9
10#[cfg(any(feature = "composite-disk", feature = "qcow"))]
11use std::fs::OpenOptions;
12#[cfg(feature = "composite-disk")]
13use std::io::Write;
14use std::path::Path;
15
16use anyhow::anyhow;
17use anyhow::Context;
18use anyhow::Result;
19use argh::FromArgs;
20use base::debug;
21use base::error;
22use base::info;
23use base::set_thread_name;
24use base::syslog;
25use base::syslog::LogArgs;
26use base::syslog::LogConfig;
27use cmdline::RunCommand;
28mod crosvm;
29use crosvm::cmdline;
30use crosvm::config::Config;
31use devices::virtio::vhost_user_backend::run_block_device;
32#[cfg(feature = "gpu")]
33use devices::virtio::vhost_user_backend::run_gpu_device;
34#[cfg(feature = "net")]
35use devices::virtio::vhost_user_backend::run_net_device;
36#[cfg(feature = "audio")]
37use devices::virtio::vhost_user_backend::run_snd_device;
38#[cfg(feature = "composite-disk")]
39use disk::create_composite_disk;
40#[cfg(feature = "composite-disk")]
41use disk::create_zero_filler;
42#[cfg(feature = "composite-disk")]
43use disk::open_disk_file;
44#[cfg(any(feature = "composite-disk", feature = "qcow"))]
45use disk::DiskFileParams;
46#[cfg(feature = "composite-disk")]
47use disk::ImagePartitionType;
48#[cfg(feature = "composite-disk")]
49use disk::PartitionInfo;
50#[cfg(feature = "qcow")]
51use disk::QcowFile;
52mod sys;
53use crosvm::cmdline::Command;
54use crosvm::cmdline::CrossPlatformCommands;
55use crosvm::cmdline::CrossPlatformDevicesCommands;
56#[cfg(windows)]
57use sys::windows::setup_metrics_reporting;
58#[cfg(feature = "composite-disk")]
59use uuid::Uuid;
60#[cfg(feature = "gpu")]
61use vm_control::client::do_gpu_display_add;
62#[cfg(feature = "gpu")]
63use vm_control::client::do_gpu_display_list;
64#[cfg(feature = "gpu")]
65use vm_control::client::do_gpu_display_remove;
66#[cfg(feature = "gpu")]
67use vm_control::client::do_gpu_set_display_mouse_mode;
68use vm_control::client::do_modify_battery;
69#[cfg(feature = "pci-hotplug")]
70use vm_control::client::do_net_add;
71#[cfg(feature = "pci-hotplug")]
72use vm_control::client::do_net_remove;
73use vm_control::client::do_security_key_attach;
74#[cfg(feature = "audio")]
75use vm_control::client::do_snd_mute_all;
76use vm_control::client::do_swap_status;
77use vm_control::client::do_usb_attach;
78use vm_control::client::do_usb_detach;
79use vm_control::client::do_usb_list;
80#[cfg(feature = "balloon")]
81use vm_control::client::handle_request;
82use vm_control::client::vms_request;
83#[cfg(feature = "gpu")]
84use vm_control::client::ModifyGpuResult;
85use vm_control::client::ModifyUsbResult;
86#[cfg(feature = "balloon")]
87use vm_control::BalloonControlCommand;
88use vm_control::DiskControlCommand;
89use vm_control::HotPlugDeviceInfo;
90use vm_control::HotPlugDeviceType;
91use vm_control::SnapshotCommand;
92use vm_control::SwapCommand;
93use vm_control::UsbControlResult;
94use vm_control::VmRequest;
95#[cfg(feature = "balloon")]
96use vm_control::VmResponse;
97
98use crate::sys::error_to_exit_code;
99use crate::sys::init_log;
100
101#[cfg(feature = "scudo")]
102#[global_allocator]
103static ALLOCATOR: scudo::GlobalScudoAllocator = scudo::GlobalScudoAllocator;
104
105#[repr(i32)]
106#[derive(Clone, Copy, Debug, PartialEq, Eq)]
107/// Exit code from crosvm,
108enum CommandStatus {
109    /// Exit with success. Also used to mean VM stopped successfully.
110    SuccessOrVmStop = 0,
111    /// VM requested reset.
112    VmReset = 32,
113    /// VM crashed.
114    VmCrash = 33,
115    /// VM exit due to kernel panic in guest.
116    GuestPanic = 34,
117    /// Invalid argument was given to crosvm.
118    InvalidArgs = 35,
119    /// VM exit due to vcpu stall detection.
120    WatchdogReset = 36,
121}
122
123impl CommandStatus {
124    fn message(&self) -> &'static str {
125        match self {
126            Self::SuccessOrVmStop => "exiting with success",
127            Self::VmReset => "exiting with reset",
128            Self::VmCrash => "exiting with crash",
129            Self::GuestPanic => "exiting with guest panic",
130            Self::InvalidArgs => "invalid argument",
131            Self::WatchdogReset => "exiting with watchdog reset",
132        }
133    }
134}
135
136impl From<sys::ExitState> for CommandStatus {
137    fn from(result: sys::ExitState) -> CommandStatus {
138        match result {
139            sys::ExitState::Stop => CommandStatus::SuccessOrVmStop,
140            sys::ExitState::Reset => CommandStatus::VmReset,
141            sys::ExitState::Crash => CommandStatus::VmCrash,
142            sys::ExitState::GuestPanic => CommandStatus::GuestPanic,
143            sys::ExitState::WatchdogReset => CommandStatus::WatchdogReset,
144        }
145    }
146}
147
148fn run_vm(cmd: RunCommand, log_config: LogConfig) -> Result<CommandStatus> {
149    let cfg = match TryInto::<Config>::try_into(cmd) {
150        Ok(cfg) => cfg,
151        Err(e) => {
152            eprintln!("{e}");
153            return Err(anyhow!("{}", e));
154        }
155    };
156
157    if let Some(ref name) = cfg.name {
158        set_thread_name(name).context("Failed to set the name")?;
159    }
160
161    #[cfg(feature = "crash-report")]
162    crosvm::sys::setup_emulator_crash_reporting(&cfg)?;
163
164    #[cfg(windows)]
165    setup_metrics_reporting()?;
166
167    init_log(log_config, &cfg)?;
168    cros_tracing::init();
169
170    if let Some(async_executor) = cfg.async_executor {
171        cros_async::Executor::set_default_executor_kind(async_executor)
172            .context("Failed to set the default async executor")?;
173    }
174
175    let exit_state = crate::sys::run_config(cfg)?;
176    Ok(CommandStatus::from(exit_state))
177}
178
179fn stop_vms(cmd: cmdline::StopCommand) -> std::result::Result<(), ()> {
180    vms_request(&VmRequest::Exit, cmd.socket_path)
181}
182
183fn suspend_vms(cmd: cmdline::SuspendCommand) -> std::result::Result<(), ()> {
184    if cmd.full {
185        vms_request(&VmRequest::SuspendVm, cmd.socket_path)
186    } else {
187        vms_request(&VmRequest::SuspendVcpus, cmd.socket_path)
188    }
189}
190
191fn swap_vms(cmd: cmdline::SwapCommand) -> std::result::Result<(), ()> {
192    use cmdline::SwapSubcommands::*;
193    let (req, path) = match &cmd.nested {
194        Enable(params) => (VmRequest::Swap(SwapCommand::Enable), &params.socket_path),
195        Trim(params) => (VmRequest::Swap(SwapCommand::Trim), &params.socket_path),
196        SwapOut(params) => (VmRequest::Swap(SwapCommand::SwapOut), &params.socket_path),
197        Disable(params) => (
198            VmRequest::Swap(SwapCommand::Disable {
199                slow_file_cleanup: params.slow_file_cleanup,
200            }),
201            &params.socket_path,
202        ),
203        Status(params) => (VmRequest::Swap(SwapCommand::Status), &params.socket_path),
204    };
205    if let VmRequest::Swap(SwapCommand::Status) = req {
206        do_swap_status(path)
207    } else {
208        vms_request(&req, path)
209    }
210}
211
212fn resume_vms(cmd: cmdline::ResumeCommand) -> std::result::Result<(), ()> {
213    if cmd.full {
214        vms_request(&VmRequest::ResumeVm, cmd.socket_path)
215    } else {
216        vms_request(&VmRequest::ResumeVcpus, cmd.socket_path)
217    }
218}
219
220fn powerbtn_vms(cmd: cmdline::PowerbtnCommand) -> std::result::Result<(), ()> {
221    vms_request(&VmRequest::Powerbtn, cmd.socket_path)
222}
223
224fn sleepbtn_vms(cmd: cmdline::SleepCommand) -> std::result::Result<(), ()> {
225    vms_request(&VmRequest::Sleepbtn, cmd.socket_path)
226}
227
228fn inject_gpe(cmd: cmdline::GpeCommand) -> std::result::Result<(), ()> {
229    vms_request(
230        &VmRequest::Gpe {
231            gpe: cmd.gpe,
232            clear_evt: None,
233        },
234        cmd.socket_path,
235    )
236}
237
238#[cfg(feature = "balloon")]
239fn balloon_vms(cmd: cmdline::BalloonCommand) -> std::result::Result<(), ()> {
240    let command = BalloonControlCommand::Adjust {
241        num_bytes: cmd.num_bytes,
242        wait_for_success: cmd.wait,
243    };
244    vms_request(&VmRequest::BalloonCommand(command), cmd.socket_path)
245}
246
247#[cfg(feature = "balloon")]
248fn balloon_stats(cmd: cmdline::BalloonStatsCommand) -> std::result::Result<(), ()> {
249    let command = BalloonControlCommand::Stats {};
250    let request = &VmRequest::BalloonCommand(command);
251    let response = handle_request(request, cmd.socket_path)?;
252    match serde_json::to_string_pretty(&response) {
253        Ok(response_json) => println!("{response_json}"),
254        Err(e) => {
255            error!("Failed to serialize into JSON: {}", e);
256            return Err(());
257        }
258    }
259    match response {
260        VmResponse::BalloonStats { .. } => Ok(()),
261        _ => Err(()),
262    }
263}
264
265#[cfg(feature = "balloon")]
266fn balloon_ws(cmd: cmdline::BalloonWsCommand) -> std::result::Result<(), ()> {
267    let command = BalloonControlCommand::WorkingSet {};
268    let request = &VmRequest::BalloonCommand(command);
269    let response = handle_request(request, cmd.socket_path)?;
270    match serde_json::to_string_pretty(&response) {
271        Ok(response_json) => println!("{response_json}"),
272        Err(e) => {
273            error!("Failed to serialize into JSON: {e}");
274            return Err(());
275        }
276    }
277    match response {
278        VmResponse::BalloonWS { .. } => Ok(()),
279        _ => Err(()),
280    }
281}
282
283fn modify_battery(cmd: cmdline::BatteryCommand) -> std::result::Result<(), ()> {
284    do_modify_battery(
285        cmd.socket_path,
286        &cmd.battery_type,
287        &cmd.property,
288        &cmd.target,
289    )
290}
291
292fn modify_vfio(cmd: cmdline::VfioCrosvmCommand) -> std::result::Result<(), ()> {
293    let (request, socket_path, vfio_path) = match cmd.command {
294        cmdline::VfioSubCommand::Add(c) => {
295            let request = VmRequest::HotPlugVfioCommand {
296                device: HotPlugDeviceInfo {
297                    device_type: HotPlugDeviceType::EndPoint,
298                    path: c.vfio_path.clone(),
299                    hp_interrupt: true,
300                },
301                add: true,
302            };
303            (request, c.socket_path, c.vfio_path)
304        }
305        cmdline::VfioSubCommand::Remove(c) => {
306            let request = VmRequest::HotPlugVfioCommand {
307                device: HotPlugDeviceInfo {
308                    device_type: HotPlugDeviceType::EndPoint,
309                    path: c.vfio_path.clone(),
310                    hp_interrupt: false,
311                },
312                add: false,
313            };
314            (request, c.socket_path, c.vfio_path)
315        }
316    };
317    if !vfio_path.exists() || !vfio_path.is_dir() {
318        error!("Invalid host sysfs path: {:?}", vfio_path);
319        return Err(());
320    }
321
322    vms_request(&request, socket_path)?;
323    Ok(())
324}
325
326#[cfg(feature = "pci-hotplug")]
327fn modify_virtio_net(cmd: cmdline::VirtioNetCommand) -> std::result::Result<(), ()> {
328    match cmd.command {
329        cmdline::VirtioNetSubCommand::AddTap(c) => {
330            let bus_num = do_net_add(&c.tap_name, c.socket_path).map_err(|e| {
331                error!("{}", &e);
332            })?;
333            info!("Tap device {} plugged to PCI bus {}", &c.tap_name, bus_num);
334        }
335        cmdline::VirtioNetSubCommand::RemoveTap(c) => {
336            do_net_remove(c.bus, &c.socket_path).map_err(|e| {
337                error!("Tap device remove failed: {:?}", &e);
338            })?;
339            info!("Tap device removed from PCI bus {}", &c.bus);
340        }
341    };
342
343    Ok(())
344}
345
346#[cfg(feature = "composite-disk")]
347fn parse_composite_partition_arg(
348    partition_arg: &str,
349) -> std::result::Result<(String, String, bool, Option<Uuid>), ()> {
350    let mut partition_fields = partition_arg.split(':');
351
352    let label = partition_fields.next();
353    let path = partition_fields.next();
354    let opt = partition_fields.next();
355    let part_guid = partition_fields.next();
356
357    if let (Some(label), Some(path)) = (label, path) {
358        // By default, composite disk is read-only
359        let writable = match opt {
360            None => false,
361            Some("") => false,
362            Some("writable") => true,
363            Some(value) => {
364                error!(
365                    "Unrecognized option '{}'. Expected 'writable' or nothing.",
366                    value
367                );
368                return Err(());
369            }
370        };
371
372        let part_guid = part_guid
373            .map(Uuid::parse_str)
374            .transpose()
375            .map_err(|e| error!("Invalid partition GUID: {}", e))?;
376
377        Ok((label.to_owned(), path.to_owned(), writable, part_guid))
378    } else {
379        error!(
380            "Must specify label and path for partition '{}', like LABEL:PARTITION",
381            partition_arg
382        );
383        Err(())
384    }
385}
386
387#[cfg(feature = "composite-disk")]
388fn create_composite(cmd: cmdline::CreateCompositeCommand) -> std::result::Result<(), ()> {
389    use std::io::BufWriter;
390    use std::path::PathBuf;
391
392    let composite_image_path = &cmd.path;
393    let zero_filler_path = format!("{composite_image_path}.filler");
394    let header_path = format!("{composite_image_path}.header");
395    let footer_path = format!("{composite_image_path}.footer");
396
397    let mut composite_image_file = OpenOptions::new()
398        .create(true)
399        .read(true)
400        .write(true)
401        .truncate(true)
402        .open(composite_image_path)
403        .map_err(|e| {
404            error!(
405                "Failed opening composite disk image file at '{}': {}",
406                composite_image_path, e
407            );
408        })?;
409    create_zero_filler(&zero_filler_path).map_err(|e| {
410        error!(
411            "Failed to create zero filler file at '{}': {}",
412            &zero_filler_path, e
413        );
414    })?;
415    let header_file = OpenOptions::new()
416        .create(true)
417        .read(true)
418        .write(true)
419        .truncate(true)
420        .open(&header_path)
421        .map_err(|e| {
422            error!(
423                "Failed opening header image file at '{}': {}",
424                header_path, e
425            );
426        })?;
427    let mut header_buffer = BufWriter::new(header_file);
428    let footer_file = OpenOptions::new()
429        .create(true)
430        .read(true)
431        .write(true)
432        .truncate(true)
433        .open(&footer_path)
434        .map_err(|e| {
435            error!(
436                "Failed opening footer image file at '{}': {}",
437                footer_path, e
438            );
439        })?;
440    let mut footer_buffer = BufWriter::new(footer_file);
441
442    let partitions = cmd
443        .partitions
444        .into_iter()
445        .map(|partition_arg| {
446            let (label, path, writable, part_guid) = parse_composite_partition_arg(&partition_arg)?;
447
448            // Sparseness for composite disks is not user provided on Linux
449            // (e.g. via an option), and it has no runtime effect.
450            let size = open_disk_file(DiskFileParams {
451                path: PathBuf::from(&path),
452                is_read_only: !writable,
453                is_sparse_file: true,
454                is_overlapped: false,
455                is_direct: false,
456                lock: true,
457                depth: 0,
458            })
459            .map_err(|e| error!("Failed to create DiskFile instance: {}", e))?
460            .get_len()
461            .map_err(|e| error!("Failed to get length of partition image: {}", e))?;
462
463            Ok(PartitionInfo {
464                label,
465                path: Path::new(&path).to_owned(),
466                partition_type: ImagePartitionType::LinuxFilesystem,
467                writable,
468                size,
469                part_guid,
470            })
471        })
472        .collect::<Result<Vec<PartitionInfo>, ()>>()?;
473
474    create_composite_disk(
475        &partitions,
476        &PathBuf::from(zero_filler_path),
477        &PathBuf::from(header_path),
478        &mut header_buffer,
479        &PathBuf::from(footer_path),
480        &mut footer_buffer,
481        &mut composite_image_file,
482    )
483    .map_err(|e| {
484        error!(
485            "Failed to create composite disk image at '{}': {}",
486            composite_image_path, e
487        );
488    })?;
489    header_buffer.flush().map_err(|e| {
490        error!("Failed to flush header buffer: {}", e);
491    })?;
492    footer_buffer.flush().map_err(|e| {
493        error!("Failed to flush footer buffer: {}", e);
494    })?;
495
496    Ok(())
497}
498
499#[cfg(feature = "qcow")]
500fn create_qcow2(cmd: cmdline::CreateQcow2Command) -> std::result::Result<(), ()> {
501    use std::path::PathBuf;
502
503    if !(cmd.size.is_some() ^ cmd.backing_file.is_some()) {
504        println!(
505            "Create a new QCOW2 image at `PATH` of either the specified `SIZE` in bytes or
506    with a '--backing_file'."
507        );
508        return Err(());
509    }
510
511    let file = OpenOptions::new()
512        .create(true)
513        .read(true)
514        .write(true)
515        .truncate(true)
516        .open(&cmd.file_path)
517        .map_err(|e| {
518            error!("Failed opening qcow file at '{}': {}", cmd.file_path, e);
519        })?;
520
521    let params = DiskFileParams {
522        path: PathBuf::from(&cmd.file_path),
523        is_read_only: false,
524        is_sparse_file: false,
525        is_overlapped: false,
526        is_direct: false,
527        lock: true,
528        depth: 0,
529    };
530    match (cmd.size, cmd.backing_file) {
531        (Some(size), None) => QcowFile::new(file, params, size).map_err(|e| {
532            error!("Failed to create qcow file at '{}': {}", cmd.file_path, e);
533        })?,
534        (None, Some(backing_file)) => QcowFile::new_from_backing(file, params, &backing_file)
535            .map_err(|e| {
536                error!("Failed to create qcow file at '{}': {}", cmd.file_path, e);
537            })?,
538        _ => unreachable!(),
539    };
540    Ok(())
541}
542
543fn start_device(opts: cmdline::DeviceCommand) -> std::result::Result<(), ()> {
544    if let Some(async_executor) = opts.async_executor {
545        cros_async::Executor::set_default_executor_kind(async_executor)
546            .map_err(|e| error!("Failed to set the default async executor: {:#}", e))?;
547    }
548
549    let result = match opts.command {
550        cmdline::DeviceSubcommand::CrossPlatform(command) => match command {
551            CrossPlatformDevicesCommands::Block(cfg) => run_block_device(cfg),
552            #[cfg(feature = "gpu")]
553            CrossPlatformDevicesCommands::Gpu(cfg) => run_gpu_device(cfg),
554            #[cfg(feature = "net")]
555            CrossPlatformDevicesCommands::Net(cfg) => run_net_device(cfg),
556            #[cfg(feature = "audio")]
557            CrossPlatformDevicesCommands::Snd(cfg) => run_snd_device(cfg),
558        },
559        cmdline::DeviceSubcommand::Sys(command) => sys::start_device(command),
560    };
561
562    result.map_err(|e| {
563        error!("Failed to run device: {:#}", e);
564    })
565}
566
567fn disk_cmd(cmd: cmdline::DiskCommand) -> std::result::Result<(), ()> {
568    match cmd.command {
569        cmdline::DiskSubcommand::Resize(cmd) => {
570            let request = VmRequest::DiskCommand {
571                disk_index: cmd.disk_index,
572                command: DiskControlCommand::Resize {
573                    new_size: cmd.disk_size,
574                },
575            };
576            vms_request(&request, cmd.socket_path)
577        }
578    }
579}
580
581fn make_rt(cmd: cmdline::MakeRTCommand) -> std::result::Result<(), ()> {
582    vms_request(&VmRequest::MakeRT, cmd.socket_path)
583}
584
585#[cfg(feature = "gpu")]
586fn gpu_display_add(cmd: cmdline::GpuAddDisplaysCommand) -> ModifyGpuResult {
587    do_gpu_display_add(cmd.socket_path, cmd.gpu_display)
588}
589
590#[cfg(feature = "gpu")]
591fn gpu_display_list(cmd: cmdline::GpuListDisplaysCommand) -> ModifyGpuResult {
592    do_gpu_display_list(cmd.socket_path)
593}
594
595#[cfg(feature = "gpu")]
596fn gpu_display_remove(cmd: cmdline::GpuRemoveDisplaysCommand) -> ModifyGpuResult {
597    do_gpu_display_remove(cmd.socket_path, cmd.display_id)
598}
599
600#[cfg(feature = "gpu")]
601fn gpu_set_display_mouse_mode(cmd: cmdline::GpuSetDisplayMouseModeCommand) -> ModifyGpuResult {
602    do_gpu_set_display_mouse_mode(cmd.socket_path, cmd.display_id, cmd.mouse_mode)
603}
604
605#[cfg(feature = "gpu")]
606fn modify_gpu(cmd: cmdline::GpuCommand) -> std::result::Result<(), ()> {
607    let result = match cmd.command {
608        cmdline::GpuSubCommand::AddDisplays(cmd) => gpu_display_add(cmd),
609        cmdline::GpuSubCommand::ListDisplays(cmd) => gpu_display_list(cmd),
610        cmdline::GpuSubCommand::RemoveDisplays(cmd) => gpu_display_remove(cmd),
611        cmdline::GpuSubCommand::SetDisplayMouseMode(cmd) => gpu_set_display_mouse_mode(cmd),
612    };
613    match result {
614        Ok(response) => {
615            println!("{response}");
616            Ok(())
617        }
618        Err(e) => {
619            println!("error {e}");
620            Err(())
621        }
622    }
623}
624
625#[cfg(feature = "audio")]
626fn modify_snd(cmd: cmdline::SndCommand) -> std::result::Result<(), ()> {
627    match cmd.command {
628        cmdline::SndSubCommand::MuteAll(cmd) => do_snd_mute_all(cmd.socket_path, cmd.muted),
629    }
630}
631
632fn usb_attach(cmd: cmdline::UsbAttachCommand) -> ModifyUsbResult<UsbControlResult> {
633    let dev_path = Path::new(&cmd.dev_path);
634
635    do_usb_attach(cmd.socket_path, dev_path)
636}
637
638fn security_key_attach(cmd: cmdline::UsbAttachKeyCommand) -> ModifyUsbResult<UsbControlResult> {
639    let dev_path = Path::new(&cmd.dev_path);
640
641    do_security_key_attach(cmd.socket_path, dev_path)
642}
643
644fn usb_detach(cmd: cmdline::UsbDetachCommand) -> ModifyUsbResult<UsbControlResult> {
645    do_usb_detach(cmd.socket_path, cmd.port)
646}
647
648fn usb_list(cmd: cmdline::UsbListCommand) -> ModifyUsbResult<UsbControlResult> {
649    do_usb_list(cmd.socket_path)
650}
651
652fn modify_usb(cmd: cmdline::UsbCommand) -> std::result::Result<(), ()> {
653    let result = match cmd.command {
654        cmdline::UsbSubCommand::Attach(cmd) => usb_attach(cmd),
655        cmdline::UsbSubCommand::SecurityKeyAttach(cmd) => security_key_attach(cmd),
656        cmdline::UsbSubCommand::Detach(cmd) => usb_detach(cmd),
657        cmdline::UsbSubCommand::List(cmd) => usb_list(cmd),
658    };
659    match result {
660        Ok(response) => {
661            println!("{response}");
662            Ok(())
663        }
664        Err(e) => {
665            println!("error {e}");
666            Err(())
667        }
668    }
669}
670
671fn snapshot_vm(cmd: cmdline::SnapshotCommand) -> std::result::Result<(), ()> {
672    use cmdline::SnapshotSubCommands::*;
673    let (socket_path, request) = match cmd.snapshot_command {
674        Take(take_cmd) => {
675            let req = VmRequest::Snapshot(SnapshotCommand::Take {
676                snapshot_path: take_cmd.snapshot_path,
677                compress_memory: take_cmd.compress_memory,
678                encrypt: take_cmd.encrypt,
679            });
680            (take_cmd.socket_path, req)
681        }
682    };
683    let socket_path = Path::new(&socket_path);
684    vms_request(&request, socket_path)
685}
686
687#[allow(clippy::unnecessary_wraps)]
688fn pkg_version() -> std::result::Result<(), ()> {
689    const VERSION: Option<&'static str> = option_env!("CARGO_PKG_VERSION");
690    const PKG_VERSION: Option<&'static str> = option_env!("PKG_VERSION");
691
692    print!("crosvm {}", VERSION.unwrap_or("UNKNOWN"));
693    match PKG_VERSION {
694        Some(v) => println!("-{v}"),
695        None => println!(),
696    }
697    Ok(())
698}
699
700// Returns true if the argument is a flag (e.g. `-s` or `--long`).
701//
702// As a special case, `-` is not treated as a flag, since it is typically used to represent
703// `stdin`/`stdout`.
704fn is_flag(arg: &str) -> bool {
705    arg.len() > 1 && arg.starts_with('-')
706}
707
708// Perform transformations on `args_iter` to produce arguments suitable for parsing by `argh`.
709fn prepare_argh_args<I: IntoIterator<Item = String>>(args_iter: I) -> Vec<String> {
710    let mut args: Vec<String> = Vec::default();
711    // http://b/235882579
712    for arg in args_iter {
713        match arg.as_str() {
714            "--host_ip" => {
715                eprintln!("`--host_ip` option is deprecated!");
716                eprintln!("Please use `--host-ip` instead");
717                args.push("--host-ip".to_string());
718            }
719            "-h" => args.push("--help".to_string()),
720            arg if is_flag(arg) => {
721                // Split `--arg=val` into `--arg val`, since argh doesn't support the former.
722                if let Some((key, value)) = arg.split_once('=') {
723                    args.push(key.to_string());
724                    args.push(value.to_string());
725                } else {
726                    args.push(arg.to_string());
727                }
728            }
729            arg => args.push(arg.to_string()),
730        }
731    }
732
733    args
734}
735
736fn shorten_usage(help: &str) -> String {
737    let mut lines = help.lines().collect::<Vec<_>>();
738    let first_line = lines[0].split(char::is_whitespace).collect::<Vec<_>>();
739
740    // Shorten the usage line if it's for `crovm run` command that has so many options.
741    let run_usage = format!("Usage: {} run <options> KERNEL", first_line[1]);
742    if first_line[0] == "Usage:" && first_line[2] == "run" {
743        lines[0] = &run_usage;
744    }
745
746    lines.join("\n")
747}
748
749fn crosvm_main<I: IntoIterator<Item = String>>(args: I) -> Result<CommandStatus> {
750    // The following panic hook will stop our crashpad hook on windows.
751    // Only initialize when the crash-pad feature is off.
752    #[cfg(not(feature = "crash-report"))]
753    sys::set_panic_hook();
754
755    // Ensure all processes detach from metrics on exit.
756    #[cfg(windows)]
757    let _metrics_destructor = metrics::get_destructor();
758
759    let args = prepare_argh_args(args);
760    let args = args.iter().map(|s| s.as_str()).collect::<Vec<_>>();
761    let args = match crosvm::cmdline::CrosvmCmdlineArgs::from_args(&args[..1], &args[1..]) {
762        Ok(args) => args,
763        Err(e) if e.status.is_ok() => {
764            // If parsing succeeded and the user requested --help, print the usage message to stdout
765            // and exit with success.
766            let help = shorten_usage(&e.output);
767            println!("{help}");
768            return Ok(CommandStatus::SuccessOrVmStop);
769        }
770        Err(e) => {
771            error!("arg parsing failed: {}", e.output);
772            return Ok(CommandStatus::InvalidArgs);
773        }
774    };
775    let extended_status = args.extended_status;
776
777    debug!("CLI arguments parsed.");
778
779    let mut log_config = LogConfig {
780        log_args: LogArgs {
781            filter: args.log_level,
782            proc_name: args.syslog_tag.unwrap_or("crosvm".to_string()),
783            syslog: !args.no_syslog,
784            ..Default::default()
785        },
786
787        ..Default::default()
788    };
789
790    let ret = match args.command {
791        Command::CrossPlatform(command) => {
792            // Past this point, usage of exit is in danger of leaking zombie processes.
793            if let CrossPlatformCommands::Run(cmd) = command {
794                if let Some(syslog_tag) = &cmd.syslog_tag {
795                    base::warn!(
796                        "`crosvm run --syslog-tag` is deprecated; please use \
797                         `crosvm --syslog-tag=\"{}\" run` instead",
798                        syslog_tag
799                    );
800                    log_config.log_args.proc_name.clone_from(syslog_tag);
801                }
802                // We handle run_vm separately because it does not simply signal success/error
803                // but also indicates whether the guest requested reset or stop.
804                run_vm(cmd, log_config)
805            } else if let CrossPlatformCommands::Device(cmd) = command {
806                // On windows, the device command handles its own logging setup, so we can't handle
807                // it below otherwise logging will double init.
808                if cfg!(unix) {
809                    syslog::init_with(log_config).context("failed to initialize syslog")?;
810                }
811                start_device(cmd)
812                    .map_err(|_| anyhow!("start_device subcommand failed"))
813                    .map(|_| CommandStatus::SuccessOrVmStop)
814            } else {
815                syslog::init_with(log_config).context("failed to initialize syslog")?;
816
817                match command {
818                    #[cfg(feature = "balloon")]
819                    CrossPlatformCommands::Balloon(cmd) => {
820                        balloon_vms(cmd).map_err(|_| anyhow!("balloon subcommand failed"))
821                    }
822                    #[cfg(feature = "balloon")]
823                    CrossPlatformCommands::BalloonStats(cmd) => {
824                        balloon_stats(cmd).map_err(|_| anyhow!("balloon_stats subcommand failed"))
825                    }
826                    #[cfg(feature = "balloon")]
827                    CrossPlatformCommands::BalloonWs(cmd) => {
828                        balloon_ws(cmd).map_err(|_| anyhow!("balloon_ws subcommand failed"))
829                    }
830                    CrossPlatformCommands::Battery(cmd) => {
831                        modify_battery(cmd).map_err(|_| anyhow!("battery subcommand failed"))
832                    }
833                    #[cfg(feature = "composite-disk")]
834                    CrossPlatformCommands::CreateComposite(cmd) => create_composite(cmd)
835                        .map_err(|_| anyhow!("create_composite subcommand failed")),
836                    #[cfg(feature = "qcow")]
837                    CrossPlatformCommands::CreateQcow2(cmd) => {
838                        create_qcow2(cmd).map_err(|_| anyhow!("create_qcow2 subcommand failed"))
839                    }
840                    CrossPlatformCommands::Device(_) => unreachable!(),
841                    CrossPlatformCommands::Disk(cmd) => {
842                        disk_cmd(cmd).map_err(|_| anyhow!("disk subcommand failed"))
843                    }
844                    #[cfg(feature = "gpu")]
845                    CrossPlatformCommands::Gpu(cmd) => {
846                        modify_gpu(cmd).map_err(|_| anyhow!("gpu subcommand failed"))
847                    }
848                    #[cfg(feature = "audio")]
849                    CrossPlatformCommands::Snd(cmd) => {
850                        modify_snd(cmd).map_err(|_| anyhow!("snd command failed"))
851                    }
852                    CrossPlatformCommands::MakeRT(cmd) => {
853                        make_rt(cmd).map_err(|_| anyhow!("make_rt subcommand failed"))
854                    }
855                    CrossPlatformCommands::Resume(cmd) => {
856                        resume_vms(cmd).map_err(|_| anyhow!("resume subcommand failed"))
857                    }
858                    CrossPlatformCommands::Run(_) => unreachable!(),
859                    CrossPlatformCommands::Stop(cmd) => {
860                        stop_vms(cmd).map_err(|_| anyhow!("stop subcommand failed"))
861                    }
862                    CrossPlatformCommands::Suspend(cmd) => {
863                        suspend_vms(cmd).map_err(|_| anyhow!("suspend subcommand failed"))
864                    }
865                    CrossPlatformCommands::Swap(cmd) => {
866                        swap_vms(cmd).map_err(|_| anyhow!("swap subcommand failed"))
867                    }
868                    CrossPlatformCommands::Powerbtn(cmd) => {
869                        powerbtn_vms(cmd).map_err(|_| anyhow!("powerbtn subcommand failed"))
870                    }
871                    CrossPlatformCommands::Sleepbtn(cmd) => {
872                        sleepbtn_vms(cmd).map_err(|_| anyhow!("sleepbtn subcommand failed"))
873                    }
874                    CrossPlatformCommands::Gpe(cmd) => {
875                        inject_gpe(cmd).map_err(|_| anyhow!("gpe subcommand failed"))
876                    }
877                    CrossPlatformCommands::Usb(cmd) => {
878                        modify_usb(cmd).map_err(|_| anyhow!("usb subcommand failed"))
879                    }
880                    CrossPlatformCommands::Version(_) => {
881                        pkg_version().map_err(|_| anyhow!("version subcommand failed"))
882                    }
883                    CrossPlatformCommands::Vfio(cmd) => {
884                        modify_vfio(cmd).map_err(|_| anyhow!("vfio subcommand failed"))
885                    }
886                    #[cfg(feature = "pci-hotplug")]
887                    CrossPlatformCommands::VirtioNet(cmd) => {
888                        modify_virtio_net(cmd).map_err(|_| anyhow!("virtio subcommand failed"))
889                    }
890                    CrossPlatformCommands::Snapshot(cmd) => {
891                        snapshot_vm(cmd).map_err(|_| anyhow!("snapshot subcommand failed"))
892                    }
893                }
894                .map(|_| CommandStatus::SuccessOrVmStop)
895            }
896        }
897        cmdline::Command::Sys(command) => {
898            let log_args = log_config.log_args.clone();
899            // On windows, the sys commands handle their own logging setup, so we can't handle it
900            // below otherwise logging will double init.
901            if cfg!(unix) {
902                syslog::init_with(log_config).context("failed to initialize syslog")?;
903            }
904            sys::run_command(command, log_args).map(|_| CommandStatus::SuccessOrVmStop)
905        }
906    };
907
908    sys::cleanup();
909
910    // WARNING: Any code added after this point is not guaranteed to run
911    // since we may forcibly kill this process (and its children) above.
912    ret.map(|s| {
913        if extended_status {
914            s
915        } else {
916            CommandStatus::SuccessOrVmStop
917        }
918    })
919}
920
921fn main() {
922    syslog::early_init();
923    debug!("crosvm started.");
924    let res = crosvm_main(std::env::args());
925
926    let exit_code = match &res {
927        Ok(code) => {
928            info!("{}", code.message());
929            *code as i32
930        }
931        Err(e) => {
932            let exit_code = error_to_exit_code(&res);
933            error!("exiting with error {}: {:?}", exit_code, e);
934            exit_code
935        }
936    };
937    std::process::exit(exit_code);
938}
939
940#[cfg(test)]
941mod tests {
942    use super::*;
943
944    #[test]
945    fn args_is_flag() {
946        assert!(is_flag("--test"));
947        assert!(is_flag("-s"));
948
949        assert!(!is_flag("-"));
950        assert!(!is_flag("no-leading-dash"));
951    }
952
953    #[test]
954    fn args_split_long() {
955        assert_eq!(
956            prepare_argh_args(
957                ["crosvm", "run", "--something=options", "vm_kernel"].map(|x| x.to_string())
958            ),
959            ["crosvm", "run", "--something", "options", "vm_kernel"]
960        );
961    }
962
963    #[test]
964    fn args_split_short() {
965        assert_eq!(
966            prepare_argh_args(
967                ["crosvm", "run", "-p=init=/bin/bash", "vm_kernel"].map(|x| x.to_string())
968            ),
969            ["crosvm", "run", "-p", "init=/bin/bash", "vm_kernel"]
970        );
971    }
972
973    #[test]
974    fn args_host_ip() {
975        assert_eq!(
976            prepare_argh_args(
977                ["crosvm", "run", "--host_ip", "1.2.3.4", "vm_kernel"].map(|x| x.to_string())
978            ),
979            ["crosvm", "run", "--host-ip", "1.2.3.4", "vm_kernel"]
980        );
981    }
982
983    #[test]
984    fn args_h() {
985        assert_eq!(
986            prepare_argh_args(["crosvm", "run", "-h"].map(|x| x.to_string())),
987            ["crosvm", "run", "--help"]
988        );
989    }
990
991    #[test]
992    fn args_battery_option() {
993        assert_eq!(
994            prepare_argh_args(
995                [
996                    "crosvm",
997                    "run",
998                    "--battery",
999                    "type=goldfish",
1000                    "-p",
1001                    "init=/bin/bash",
1002                    "vm_kernel"
1003                ]
1004                .map(|x| x.to_string())
1005            ),
1006            [
1007                "crosvm",
1008                "run",
1009                "--battery",
1010                "type=goldfish",
1011                "-p",
1012                "init=/bin/bash",
1013                "vm_kernel"
1014            ]
1015        );
1016    }
1017
1018    #[test]
1019    fn help_success() {
1020        let args = ["crosvm", "--help"];
1021        let res = crosvm_main(args.iter().map(|s| s.to_string()));
1022        let status = res.expect("arg parsing should succeed");
1023        assert_eq!(status, CommandStatus::SuccessOrVmStop);
1024    }
1025
1026    #[test]
1027    fn invalid_arg_failure() {
1028        let args = ["crosvm", "--heeeelp"];
1029        let res = crosvm_main(args.iter().map(|s| s.to_string()));
1030        let status = res.expect("arg parsing should succeed");
1031        assert_eq!(status, CommandStatus::InvalidArgs);
1032    }
1033
1034    #[test]
1035    #[cfg(feature = "composite-disk")]
1036    fn parse_composite_disk_arg() {
1037        let arg1 = String::from("LABEL1:/partition1.img:writable");
1038        let res1 = parse_composite_partition_arg(&arg1);
1039        assert_eq!(
1040            res1,
1041            Ok((
1042                String::from("LABEL1"),
1043                String::from("/partition1.img"),
1044                true,
1045                None
1046            ))
1047        );
1048
1049        let arg2 = String::from("LABEL2:/partition2.img");
1050        let res2 = parse_composite_partition_arg(&arg2);
1051        assert_eq!(
1052            res2,
1053            Ok((
1054                String::from("LABEL2"),
1055                String::from("/partition2.img"),
1056                false,
1057                None
1058            ))
1059        );
1060
1061        let arg3 =
1062            String::from("LABEL3:/partition3.img:writable:4049C8DC-6C2B-C740-A95A-BDAA629D4378");
1063        let res3 = parse_composite_partition_arg(&arg3);
1064        assert_eq!(
1065            res3,
1066            Ok((
1067                String::from("LABEL3"),
1068                String::from("/partition3.img"),
1069                true,
1070                Some(Uuid::from_u128(0x4049C8DC_6C2B_C740_A95A_BDAA629D4378))
1071            ))
1072        );
1073
1074        // third argument is an empty string. writable: false.
1075        let arg4 = String::from("LABEL4:/partition4.img::4049C8DC-6C2B-C740-A95A-BDAA629D4378");
1076        let res4 = parse_composite_partition_arg(&arg4);
1077        assert_eq!(
1078            res4,
1079            Ok((
1080                String::from("LABEL4"),
1081                String::from("/partition4.img"),
1082                false,
1083                Some(Uuid::from_u128(0x4049C8DC_6C2B_C740_A95A_BDAA629D4378))
1084            ))
1085        );
1086
1087        // third argument is not "writable" or an empty string
1088        let arg5 = String::from("LABEL5:/partition5.img:4049C8DC-6C2B-C740-A95A-BDAA629D4378");
1089        let res5 = parse_composite_partition_arg(&arg5);
1090        assert_eq!(res5, Err(()));
1091    }
1092
1093    #[test]
1094    fn test_shorten_run_usage() {
1095        let help = r"Usage: crosvm run [<KERNEL>] [options] <very long line>...
1096
1097Start a new crosvm instance";
1098        assert_eq!(
1099            shorten_usage(help),
1100            r"Usage: crosvm run <options> KERNEL
1101
1102Start a new crosvm instance"
1103        );
1104    }
1105}