openvmm_entry/
crash_dump.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Guest crash dump helpers.
5
6use anyhow::Context;
7use futures::StreamExt;
8use futures_concurrency::stream::Merge;
9use get_resources::crash::GuestCrashDeviceHandle;
10use mesh::OneshotReceiver;
11use mesh::channel;
12use mesh::rpc::FailableRpc;
13use pal_async::task::Spawn;
14use pal_async::task::Task;
15use std::fs::File;
16use std::path::Path;
17use std::path::PathBuf;
18use unicycle::FuturesUnordered;
19use vm_resource::IntoResource;
20use vm_resource::Resource;
21use vm_resource::kind::VmbusDeviceHandleKind;
22
23/// Spawns a crash dump handling task and returns a resource to instantiate a
24/// guest crash device.
25pub fn spawn_dump_handler(
26    spawner: impl Spawn,
27    dump_path: PathBuf,
28    max_file_size: Option<u64>,
29) -> (Resource<VmbusDeviceHandleKind>, Task<()>) {
30    const DEFAULT_MAX_DUMP_SIZE: u64 = 256 * 1024 * 1024;
31
32    let (send, recv) = channel::<FailableRpc<_, _>>();
33    let task = spawner.spawn("crash_dumps", async move {
34        handle_dump_requests(&dump_path, recv).await
35    });
36    let config = GuestCrashDeviceHandle {
37        request_dump: send,
38        max_dump_size: max_file_size.unwrap_or(DEFAULT_MAX_DUMP_SIZE),
39    };
40    (config.into_resource(), task)
41}
42
43/// Handles dump requests from the crash dump device by opening files in the
44/// provided path.
45pub async fn handle_dump_requests(
46    dump_path: &Path,
47    mut recv: mesh::Receiver<
48        mesh::rpc::Rpc<OneshotReceiver<()>, Result<File, mesh::error::RemoteError>>,
49    >,
50) {
51    let mut tasks = FuturesUnordered::new();
52    while let Some(rpc) = ((&mut recv).map(Some), (&mut tasks).map(|()| None))
53        .merge()
54        .next()
55        .await
56    {
57        let Some(rpc) = rpc else { continue };
58        rpc.handle_failable_sync(|done| {
59            let tempfile = tempfile::Builder::new()
60                .prefix("underhill.")
61                .suffix(".core")
62                .tempfile_in(dump_path)
63                .context("failed to create file")?;
64
65            let file = tempfile
66                .as_file()
67                .try_clone()
68                .context("failed to clone file")?;
69
70            tracing::info!(path = %tempfile.path().display(), "writing VTL2 crash dump");
71            tasks.push(wait_for_dump(done, tempfile));
72            anyhow::Ok(file)
73        })
74    }
75}
76
77async fn wait_for_dump(done: OneshotReceiver<()>, tempfile: tempfile::NamedTempFile) {
78    if let Ok(()) = done.await {
79        match tempfile.keep() {
80            Ok((_, path)) => {
81                tracing::info!(
82                    path = %path.display(),
83                    "wrote VTL2 crash dump"
84                );
85            }
86            Err(err) => {
87                tracing::error!(
88                    path = %err.file.path().display(),
89                    "failed to persist VTL2 dump file"
90                );
91            }
92        }
93    } else {
94        tracing::info!(
95            path = %tempfile.path().display(),
96            "VTL2 crash dump did not complete, removing"
97        );
98        drop(tempfile);
99    }
100}