scsidisk/
resolver.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Resolver for SCSI devices.
5
6use crate::SimpleScsiDisk;
7use crate::scsidvd::SimpleScsiDvd;
8use anyhow::Context;
9use async_trait::async_trait;
10use disk_backend::resolve::ResolveDiskParameters;
11use futures::StreamExt;
12use pal_async::task::Spawn;
13use scsi_core::ResolveScsiDeviceHandleParams;
14use scsi_core::ResolvedScsiDevice;
15use scsidisk_resources::SimpleScsiDiskHandle;
16use scsidisk_resources::SimpleScsiDvdHandle;
17use scsidisk_resources::SimpleScsiDvdRequest;
18use std::sync::Arc;
19use std::sync::Weak;
20use thiserror::Error;
21use vm_resource::AsyncResolveResource;
22use vm_resource::ResolveError;
23use vm_resource::ResourceResolver;
24use vm_resource::declare_static_async_resolver;
25use vm_resource::kind::ScsiDeviceHandleKind;
26
27/// A resolver for [`SimpleScsiDiskHandle`] and [`SimpleScsiDvdHandle`].
28pub struct SimpleScsiResolver;
29
30declare_static_async_resolver! {
31    SimpleScsiResolver,
32    (ScsiDeviceHandleKind, SimpleScsiDiskHandle),
33    (ScsiDeviceHandleKind, SimpleScsiDvdHandle),
34}
35
36#[derive(Debug, Error)]
37pub enum Error {
38    #[error("failed to resolve backing disk")]
39    Disk(#[source] ResolveError),
40}
41
42#[async_trait]
43impl AsyncResolveResource<ScsiDeviceHandleKind, SimpleScsiDiskHandle> for SimpleScsiResolver {
44    type Output = ResolvedScsiDevice;
45    type Error = Error;
46
47    async fn resolve(
48        &self,
49        resolver: &ResourceResolver,
50        resource: SimpleScsiDiskHandle,
51        _: ResolveScsiDeviceHandleParams<'_>,
52    ) -> Result<Self::Output, Self::Error> {
53        let disk = resolver
54            .resolve(
55                resource.disk,
56                ResolveDiskParameters {
57                    read_only: resource.read_only,
58                    _async_trait_workaround: &(),
59                },
60            )
61            .await
62            .map_err(Error::Disk)?;
63
64        let disk = SimpleScsiDisk::new(disk.0, resource.parameters);
65        Ok(disk.into())
66    }
67}
68
69#[async_trait]
70impl AsyncResolveResource<ScsiDeviceHandleKind, SimpleScsiDvdHandle> for SimpleScsiResolver {
71    type Output = ResolvedScsiDevice;
72    type Error = Error;
73
74    async fn resolve(
75        &self,
76        resolver: &ResourceResolver,
77        resource: SimpleScsiDvdHandle,
78        input: ResolveScsiDeviceHandleParams<'_>,
79    ) -> Result<Self::Output, Self::Error> {
80        let media = if let Some(media) = resource.media {
81            Some(
82                resolver
83                    .resolve(
84                        media,
85                        ResolveDiskParameters {
86                            read_only: true,
87                            _async_trait_workaround: &(),
88                        },
89                    )
90                    .await
91                    .map_err(Error::Disk)?
92                    .0,
93            )
94        } else {
95            None
96        };
97        let dvd = Arc::new(SimpleScsiDvd::new(media));
98
99        // Start a task to handle incoming change media requests.
100        if let Some(requests) = resource.requests {
101            input
102                .driver_source
103                .simple()
104                .spawn(
105                    "dvd-requests",
106                    handle_dvd_requests(Arc::downgrade(&dvd), resolver.clone(), requests),
107                )
108                .detach();
109        }
110
111        Ok(ResolvedScsiDevice(dvd))
112    }
113}
114
115async fn handle_dvd_requests(
116    dvd: Weak<SimpleScsiDvd>,
117    resolver: ResourceResolver,
118    mut requests: mesh::Receiver<SimpleScsiDvdRequest>,
119) {
120    while let Some(req) = requests.next().await {
121        match req {
122            SimpleScsiDvdRequest::ChangeMedia(rpc) => {
123                rpc.handle_failable(async |resource| {
124                    let media = if let Some(resource) = resource {
125                        Some(
126                            resolver
127                                .resolve(
128                                    resource,
129                                    ResolveDiskParameters {
130                                        read_only: true,
131                                        _async_trait_workaround: &(),
132                                    },
133                                )
134                                .await
135                                .context("failed to resolve media")?
136                                .0,
137                        )
138                    } else {
139                        None
140                    };
141                    if let Some(dvd) = dvd.upgrade() {
142                        dvd.change_media(media);
143                    }
144                    anyhow::Ok(())
145                })
146                .await
147            }
148        }
149    }
150}