vmm/vmm_config/
pmem.rs

1// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::sync::{Arc, Mutex};
5
6use serde::{Deserialize, Serialize};
7
8use crate::devices::virtio::pmem::device::{Pmem, PmemError};
9
10/// Errors associated wit the operations allowed on a pmem device
11#[derive(Debug, thiserror::Error, displaydoc::Display)]
12pub enum PmemConfigError {
13    /// Attempt to add pmem as a root device while the root device defined as a block device
14    AddingSecondRootDevice,
15    /// A root pmem device already exist
16    RootPmemDeviceAlreadyExist,
17    /// Unable to create the virtio-pmem device: {0}
18    CreateDevice(#[from] PmemError),
19    /// Error accessing underlying file: {0}
20    File(std::io::Error),
21}
22
23/// Use this structure to setup a Pmem device before boothing the kernel.
24#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
25#[serde(deny_unknown_fields)]
26pub struct PmemConfig {
27    /// Unique identifier of the device.
28    pub id: String,
29    /// Path of the drive.
30    pub path_on_host: String,
31    /// Use this pmem device for rootfs
32    #[serde(default)]
33    pub root_device: bool,
34    /// Map the file as read only
35    #[serde(default)]
36    pub read_only: bool,
37}
38
39/// Wrapper for the collection that holds all the Pmem devices.
40#[derive(Debug, Default)]
41pub struct PmemBuilder {
42    /// The list of pmem devices
43    pub devices: Vec<Arc<Mutex<Pmem>>>,
44}
45
46impl PmemBuilder {
47    /// Specifies whether there is a root block device already present in the list.
48    pub fn has_root_device(&self) -> bool {
49        self.devices
50            .iter()
51            .any(|d| d.lock().unwrap().config.root_device)
52    }
53
54    /// Build a device from the config
55    pub fn build(
56        &mut self,
57        config: PmemConfig,
58        has_block_root: bool,
59    ) -> Result<(), PmemConfigError> {
60        if config.root_device && has_block_root {
61            return Err(PmemConfigError::AddingSecondRootDevice);
62        }
63        let position = self
64            .devices
65            .iter()
66            .position(|d| d.lock().unwrap().config.id == config.id);
67        if let Some(index) = position {
68            if !self.devices[index].lock().unwrap().config.root_device
69                && config.root_device
70                && self.has_root_device()
71            {
72                return Err(PmemConfigError::RootPmemDeviceAlreadyExist);
73            }
74            let pmem = Pmem::new(config)?;
75            let pmem = Arc::new(Mutex::new(pmem));
76            self.devices[index] = pmem;
77        } else {
78            if config.root_device && self.has_root_device() {
79                return Err(PmemConfigError::RootPmemDeviceAlreadyExist);
80            }
81            let pmem = Pmem::new(config)?;
82            let pmem = Arc::new(Mutex::new(pmem));
83            self.devices.push(pmem);
84        }
85        Ok(())
86    }
87
88    /// Adds an existing pmem device in the builder. This function should
89    /// only be used during snapshot restoration process and should add
90    /// devices in the same order as they were in the original VM.
91    pub fn add_device(&mut self, device: Arc<Mutex<Pmem>>) {
92        self.devices.push(device);
93    }
94
95    /// Returns a vec with the structures used to configure the devices.
96    pub fn configs(&self) -> Vec<PmemConfig> {
97        self.devices
98            .iter()
99            .map(|b| b.lock().unwrap().config.clone())
100            .collect()
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use vmm_sys_util::tempfile::TempFile;
107
108    use super::*;
109
110    #[test]
111    fn test_pmem_builder_build() {
112        let mut builder = PmemBuilder::default();
113
114        let dummy_file = TempFile::new().unwrap();
115        dummy_file.as_file().set_len(Pmem::ALIGNMENT).unwrap();
116        let dummy_path = dummy_file.as_path().to_str().unwrap().to_string();
117        let mut config = PmemConfig {
118            id: "1".into(),
119            path_on_host: dummy_path,
120            root_device: true,
121            read_only: false,
122        };
123        builder.build(config.clone(), false).unwrap();
124        assert_eq!(builder.devices.len(), 1);
125        assert!(builder.has_root_device());
126
127        // First device got replaced with new one
128        config.root_device = false;
129        builder.build(config, false).unwrap();
130        assert_eq!(builder.devices.len(), 1);
131        assert!(!builder.has_root_device());
132    }
133
134    #[test]
135    fn test_pmem_builder_build_seconde_root() {
136        let mut builder = PmemBuilder::default();
137
138        let dummy_file = TempFile::new().unwrap();
139        dummy_file.as_file().set_len(Pmem::ALIGNMENT).unwrap();
140        let dummy_path = dummy_file.as_path().to_str().unwrap().to_string();
141        let mut config = PmemConfig {
142            id: "1".into(),
143            path_on_host: dummy_path,
144            root_device: true,
145            read_only: false,
146        };
147        builder.build(config.clone(), false).unwrap();
148
149        config.id = "2".into();
150        assert!(matches!(
151            builder.build(config.clone(), false).unwrap_err(),
152            PmemConfigError::RootPmemDeviceAlreadyExist,
153        ));
154    }
155
156    #[test]
157    fn test_pmem_builder_build_root_with_block_already_a_root() {
158        let mut builder = PmemBuilder::default();
159
160        let dummy_file = TempFile::new().unwrap();
161        dummy_file.as_file().set_len(Pmem::ALIGNMENT).unwrap();
162        let dummy_path = dummy_file.as_path().to_str().unwrap().to_string();
163        let config = PmemConfig {
164            id: "1".into(),
165            path_on_host: dummy_path,
166            root_device: true,
167            read_only: false,
168        };
169        assert!(matches!(
170            builder.build(config, true).unwrap_err(),
171            PmemConfigError::AddingSecondRootDevice,
172        ));
173    }
174}