devices/virtio/scsi/
mod.rs

1// Copyright 2023 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
5use std::path::PathBuf;
6
7use anyhow::Context;
8use serde::Deserialize;
9use serde::Serialize;
10
11use crate::virtio::VirtioDevice;
12use crate::VirtioDeviceArgs;
13use crate::VirtioDeviceModule;
14
15pub(crate) mod sys;
16
17pub mod commands;
18pub mod constants;
19mod device;
20
21pub use device::Controller;
22pub use device::DiskConfig;
23
24fn scsi_option_lock_default() -> bool {
25    true
26}
27fn scsi_option_block_size_default() -> u32 {
28    512
29}
30
31/// Parameters for setting up a SCSI device.
32#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, serde_keyvalue::FromKeyValues)]
33#[serde(deny_unknown_fields, rename_all = "kebab-case")]
34pub struct ScsiOption {
35    // Path to the SCSI image.
36    pub path: PathBuf,
37    // Indicates whether the device is ready only.
38    #[serde(default, rename = "ro")]
39    pub read_only: bool,
40    /// Whether to lock the disk files. Uses flock on Unix and FILE_SHARE_* flags on Windows.
41    #[serde(default = "scsi_option_lock_default")]
42    pub lock: bool,
43    // The block size of the device.
44    #[serde(default = "scsi_option_block_size_default")]
45    pub block_size: u32,
46    /// Whether this scsi device should be the root device. Can only be set once. Only useful for
47    /// adding specific command-line options.
48    #[serde(default)]
49    pub root: bool,
50}
51
52/// Module for creating a Virtio SCSI controller device.
53#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
54pub struct VirtioScsiModule {
55    pub disks: Vec<ScsiOption>,
56}
57
58impl VirtioScsiModule {
59    pub fn new(disks: Vec<ScsiOption>) -> Self {
60        Self { disks }
61    }
62}
63
64impl VirtioDeviceModule for VirtioScsiModule {
65    fn sort_name(&self) -> &'static str {
66        "scsi"
67    }
68
69    fn create(&self, args: &mut VirtioDeviceArgs<'_>) -> anyhow::Result<Box<dyn VirtioDevice>> {
70        let base_features = crate::virtio::base_features(args.protection_type);
71        let disks = self
72            .disks
73            .iter()
74            .map(|op| {
75                base::info!("Trying to attach a scsi device: {}", op.path.display());
76                Ok(DiskConfig {
77                    file: op.open()?,
78                    block_size: op.block_size,
79                    read_only: op.read_only,
80                })
81            })
82            .collect::<anyhow::Result<Vec<_>>>()?;
83        let controller =
84            Controller::new(base_features, disks).context("failed to create a scsi controller")?;
85        Ok(Box::new(controller))
86    }
87
88    #[cfg(any(target_os = "android", target_os = "linux"))]
89    fn create_jail(
90        &self,
91        jail_config: &jail::JailConfig,
92    ) -> anyhow::Result<Option<minijail::Minijail>> {
93        let jail = jail::simple_jail(
94            Some(jail_config),
95            &crate::virtio::VirtioDeviceType::Regular.seccomp_policy_file("scsi"),
96        )?;
97        Ok(jail)
98    }
99}
100
101#[cfg(test)]
102impl Default for ScsiOption {
103    fn default() -> Self {
104        Self {
105            path: PathBuf::new(),
106            read_only: false,
107            lock: scsi_option_lock_default(),
108            block_size: scsi_option_block_size_default(),
109            root: false,
110        }
111    }
112}
113
114#[cfg(test)]
115mod tests {
116    use std::path::Path;
117
118    use serde_keyvalue::from_key_values;
119
120    use super::*;
121
122    #[test]
123    fn parse_scsi_options() {
124        let scsi_option = from_key_values::<ScsiOption>("/path/to/image").unwrap();
125        assert_eq!(
126            scsi_option,
127            ScsiOption {
128                path: Path::new("/path/to/image").to_path_buf(),
129                ..Default::default()
130            }
131        );
132
133        let scsi_option = from_key_values::<ScsiOption>("/path/to/image,ro").unwrap();
134        assert_eq!(
135            scsi_option,
136            ScsiOption {
137                path: Path::new("/path/to/image").to_path_buf(),
138                read_only: true,
139                ..Default::default()
140            }
141        );
142
143        let scsi_option = from_key_values::<ScsiOption>("/path/to/image,block-size=1024").unwrap();
144        assert_eq!(
145            scsi_option,
146            ScsiOption {
147                path: Path::new("/path/to/image").to_path_buf(),
148                block_size: 1024,
149                ..Default::default()
150            }
151        );
152
153        let scsi_option =
154            from_key_values::<ScsiOption>("/path/to/image,block-size=1024,root").unwrap();
155        assert_eq!(
156            scsi_option,
157            ScsiOption {
158                path: Path::new("/path/to/image").to_path_buf(),
159                block_size: 1024,
160                root: true,
161                ..Default::default()
162            }
163        );
164    }
165}