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;
26use vmcore::vm_task::VmTaskDriverSource;
27
28/// A resolver for [`SimpleScsiDiskHandle`] and [`SimpleScsiDvdHandle`].
29pub struct SimpleScsiResolver;
30
31declare_static_async_resolver! {
32    SimpleScsiResolver,
33    (ScsiDeviceHandleKind, SimpleScsiDiskHandle),
34    (ScsiDeviceHandleKind, SimpleScsiDvdHandle),
35}
36
37#[derive(Debug, Error)]
38pub enum Error {
39    #[error("failed to resolve backing disk")]
40    Disk(#[source] ResolveError),
41}
42
43#[async_trait]
44impl AsyncResolveResource<ScsiDeviceHandleKind, SimpleScsiDiskHandle> for SimpleScsiResolver {
45    type Output = ResolvedScsiDevice;
46    type Error = Error;
47
48    async fn resolve(
49        &self,
50        resolver: &ResourceResolver,
51        resource: SimpleScsiDiskHandle,
52        input: ResolveScsiDeviceHandleParams<'_>,
53    ) -> Result<Self::Output, Self::Error> {
54        let disk = resolver
55            .resolve(
56                resource.disk,
57                ResolveDiskParameters {
58                    read_only: resource.read_only,
59                    driver_source: input.driver_source,
60                },
61            )
62            .await
63            .map_err(Error::Disk)?;
64
65        let disk = SimpleScsiDisk::new(disk.0, resource.parameters);
66        Ok(disk.into())
67    }
68}
69
70#[async_trait]
71impl AsyncResolveResource<ScsiDeviceHandleKind, SimpleScsiDvdHandle> for SimpleScsiResolver {
72    type Output = ResolvedScsiDevice;
73    type Error = Error;
74
75    async fn resolve(
76        &self,
77        resolver: &ResourceResolver,
78        resource: SimpleScsiDvdHandle,
79        input: ResolveScsiDeviceHandleParams<'_>,
80    ) -> Result<Self::Output, Self::Error> {
81        let media = if let Some(media) = resource.media {
82            Some(
83                resolver
84                    .resolve(
85                        media,
86                        ResolveDiskParameters {
87                            read_only: true,
88                            driver_source: input.driver_source,
89                        },
90                    )
91                    .await
92                    .map_err(Error::Disk)?
93                    .0,
94            )
95        } else {
96            None
97        };
98        let dvd = Arc::new(SimpleScsiDvd::new(media));
99
100        // Start a task to handle incoming change media requests.
101        if let Some(requests) = resource.requests {
102            input
103                .driver_source
104                .simple()
105                .spawn(
106                    "dvd-requests",
107                    handle_dvd_requests(
108                        Arc::downgrade(&dvd),
109                        resolver.clone(),
110                        input.driver_source.clone(),
111                        requests,
112                    ),
113                )
114                .detach();
115        }
116
117        Ok(ResolvedScsiDevice(dvd))
118    }
119}
120
121async fn handle_dvd_requests(
122    dvd: Weak<SimpleScsiDvd>,
123    resolver: ResourceResolver,
124    driver_source: VmTaskDriverSource,
125    mut requests: mesh::Receiver<SimpleScsiDvdRequest>,
126) {
127    while let Some(req) = requests.next().await {
128        match req {
129            SimpleScsiDvdRequest::ChangeMedia(rpc) => {
130                rpc.handle_failable(async |resource| {
131                    let media = if let Some(resource) = resource {
132                        Some(
133                            resolver
134                                .resolve(
135                                    resource,
136                                    ResolveDiskParameters {
137                                        read_only: true,
138                                        driver_source: &driver_source,
139                                    },
140                                )
141                                .await
142                                .context("failed to resolve media")?
143                                .0,
144                        )
145                    } else {
146                        None
147                    };
148                    if let Some(dvd) = dvd.upgrade() {
149                        dvd.change_media(media);
150                    }
151                    anyhow::Ok(())
152                })
153                .await
154            }
155        }
156    }
157}