nydus_storage/cache/state/
indexed_chunk_map.rs1use std::io::Result;
12
13use crate::cache::state::persist_map::PersistMap;
14use crate::cache::state::{ChunkIndexGetter, ChunkMap, RangeMap};
15use crate::device::BlobChunkInfo;
16
17const FILE_SUFFIX: &str = "chunk_map";
19
20pub struct IndexedChunkMap {
32 map: PersistMap,
33}
34
35impl IndexedChunkMap {
36 pub fn new(blob_path: &str, chunk_count: u32, persist: bool) -> Result<Self> {
38 let filename = format!("{}.{}", blob_path, FILE_SUFFIX);
39
40 PersistMap::open(&filename, chunk_count, true, persist).map(|map| IndexedChunkMap { map })
41 }
42}
43
44impl ChunkMap for IndexedChunkMap {
45 fn is_ready(&self, chunk: &dyn BlobChunkInfo) -> Result<bool> {
46 if self.is_range_all_ready() {
47 Ok(true)
48 } else {
49 let index = self.map.validate_index(chunk.id())?;
50 Ok(self.map.is_chunk_ready(index).0)
51 }
52 }
53
54 fn set_ready_and_clear_pending(&self, chunk: &dyn BlobChunkInfo) -> Result<()> {
55 self.map.set_chunk_ready(chunk.id())
56 }
57
58 fn is_persist(&self) -> bool {
59 true
60 }
61
62 fn as_range_map(&self) -> Option<&dyn RangeMap<I = u32>> {
63 Some(self)
64 }
65}
66
67impl RangeMap for IndexedChunkMap {
68 type I = u32;
69
70 #[inline]
71 fn is_range_all_ready(&self) -> bool {
72 self.map.is_range_all_ready()
73 }
74
75 fn is_range_ready(&self, start_index: u32, count: u32) -> Result<bool> {
76 if !self.is_range_all_ready() {
77 for idx in 0..count {
78 let index = self
79 .map
80 .validate_index(start_index.checked_add(idx).ok_or_else(|| einval!())?)?;
81 if !self.map.is_chunk_ready(index).0 {
82 return Ok(false);
83 }
84 }
85 }
86
87 Ok(true)
88 }
89
90 fn check_range_ready_and_mark_pending(
91 &self,
92 start_index: u32,
93 count: u32,
94 ) -> Result<Option<Vec<u32>>> {
95 if self.is_range_all_ready() {
96 return Ok(None);
97 }
98
99 let mut vec = Vec::with_capacity(count as usize);
100 let count = std::cmp::min(count, u32::MAX - start_index);
101 let end = start_index + count;
102
103 for index in start_index..end {
104 if !self.map.is_chunk_ready(index).0 {
105 vec.push(index);
106 }
107 }
108
109 if vec.is_empty() {
110 Ok(None)
111 } else {
112 Ok(Some(vec))
113 }
114 }
115
116 fn set_range_ready_and_clear_pending(&self, start_index: u32, count: u32) -> Result<()> {
117 let count = std::cmp::min(count, u32::MAX - start_index);
118 let end = start_index + count;
119
120 for index in start_index..end {
121 self.map.set_chunk_ready(index)?;
122 }
123
124 Ok(())
125 }
126}
127
128impl ChunkIndexGetter for IndexedChunkMap {
129 type Index = u32;
130
131 fn get_index(chunk: &dyn BlobChunkInfo) -> Self::Index {
132 chunk.id()
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 use std::fs::OpenOptions;
139 use std::io::Write;
140 use std::sync::atomic::Ordering;
141 use vmm_sys_util::tempdir::TempDir;
142
143 use super::super::persist_map::*;
144 use super::*;
145 use crate::device::v5::BlobV5ChunkInfo;
146 use crate::test::MockChunkInfo;
147
148 #[test]
149 fn test_indexed_new_invalid_file_size() {
150 let dir = TempDir::new().unwrap();
151 let blob_path = dir.as_path().join("blob-1");
152 let blob_path = blob_path.as_os_str().to_str().unwrap().to_string();
153
154 assert!(IndexedChunkMap::new(&blob_path, 0, false).is_err());
155
156 let cache_path = format!("{}.{}", blob_path, FILE_SUFFIX);
157 let mut file = OpenOptions::new()
158 .read(true)
159 .write(true)
160 .create(true)
161 .truncate(false)
162 .open(&cache_path)
163 .map_err(|err| {
164 einval!(format!(
165 "failed to open/create blob chunk_map file {:?}: {:?}",
166 cache_path, err
167 ))
168 })
169 .unwrap();
170 file.write_all(&[0x0u8]).unwrap();
171
172 let chunk = MockChunkInfo::new();
173 assert_eq!(chunk.id(), 0);
174
175 assert!(IndexedChunkMap::new(&blob_path, 1, true).is_err());
176 }
177
178 #[test]
179 fn test_indexed_new_zero_file_size() {
180 let dir = TempDir::new().unwrap();
181 let blob_path = dir.as_path().join("blob-1");
182 let blob_path = blob_path.as_os_str().to_str().unwrap().to_string();
183
184 assert!(IndexedChunkMap::new(&blob_path, 0, true).is_err());
185
186 let cache_path = format!("{}.{}", blob_path, FILE_SUFFIX);
187 let _file = OpenOptions::new()
188 .read(true)
189 .write(true)
190 .create(true)
191 .truncate(false)
192 .open(&cache_path)
193 .map_err(|err| {
194 einval!(format!(
195 "failed to open/create blob chunk_map file {:?}: {:?}",
196 cache_path, err
197 ))
198 })
199 .unwrap();
200
201 let chunk = MockChunkInfo::new();
202 assert_eq!(chunk.id(), 0);
203
204 let map = IndexedChunkMap::new(&blob_path, 1, true).unwrap();
205 assert_eq!(map.map.not_ready_count.load(Ordering::Acquire), 1);
206 assert_eq!(map.map.count, 1);
207 assert_eq!(map.map.size(), 0x1001);
208 assert!(!map.is_range_all_ready());
209 assert!(!map.is_ready(chunk.as_base()).unwrap());
210 map.set_ready_and_clear_pending(chunk.as_base()).unwrap();
211 assert!(map.is_ready(chunk.as_base()).unwrap());
212 }
213
214 #[test]
215 fn test_indexed_new_header_not_ready() {
216 let dir = TempDir::new().unwrap();
217 let blob_path = dir.as_path().join("blob-1");
218 let blob_path = blob_path.as_os_str().to_str().unwrap().to_string();
219
220 assert!(IndexedChunkMap::new(&blob_path, 0, true).is_err());
221
222 let cache_path = format!("{}.{}", blob_path, FILE_SUFFIX);
223 let file = OpenOptions::new()
224 .read(true)
225 .write(true)
226 .create(true)
227 .truncate(false)
228 .open(&cache_path)
229 .map_err(|err| {
230 einval!(format!(
231 "failed to open/create blob chunk_map file {:?}: {:?}",
232 cache_path, err
233 ))
234 })
235 .unwrap();
236 file.set_len(0x1001).unwrap();
237
238 let chunk = MockChunkInfo::new();
239 assert_eq!(chunk.id(), 0);
240
241 let map = IndexedChunkMap::new(&blob_path, 1, true).unwrap();
242 assert_eq!(map.map.not_ready_count.load(Ordering::Acquire), 1);
243 assert_eq!(map.map.count, 1);
244 assert_eq!(map.map.size(), 0x1001);
245 assert!(!map.is_range_all_ready());
246 assert!(!map.is_ready(chunk.as_base()).unwrap());
247 map.set_ready_and_clear_pending(chunk.as_base()).unwrap();
248 assert!(map.is_ready(chunk.as_base()).unwrap());
249 }
250
251 #[test]
252 fn test_indexed_new_all_ready() {
253 let dir = TempDir::new().unwrap();
254 let blob_path = dir.as_path().join("blob-1");
255 let blob_path = blob_path.as_os_str().to_str().unwrap().to_string();
256
257 assert!(IndexedChunkMap::new(&blob_path, 0, true).is_err());
258
259 let cache_path = format!("{}.{}", blob_path, FILE_SUFFIX);
260 let mut file = OpenOptions::new()
261 .read(true)
262 .write(true)
263 .create(true)
264 .truncate(false)
265 .open(&cache_path)
266 .map_err(|err| {
267 einval!(format!(
268 "failed to open/create blob chunk_map file {:?}: {:?}",
269 cache_path, err
270 ))
271 })
272 .unwrap();
273 let header = Header {
274 magic: MAGIC1,
275 version: 1,
276 magic2: MAGIC2,
277 all_ready: MAGIC_ALL_READY,
278 reserved: [0x0u8; HEADER_RESERVED_SIZE],
279 };
280
281 file.write_all(header.as_slice()).unwrap();
283 file.write_all(&[0x0u8]).unwrap();
284
285 let chunk = MockChunkInfo::new();
286 assert_eq!(chunk.id(), 0);
287
288 let map = IndexedChunkMap::new(&blob_path, 1, true).unwrap();
289 assert!(map.is_range_all_ready());
290 assert_eq!(map.map.count, 1);
291 assert_eq!(map.map.size(), 0x1001);
292 assert!(map.is_ready(chunk.as_base()).unwrap());
293 map.set_ready_and_clear_pending(chunk.as_base()).unwrap();
294 assert!(map.is_ready(chunk.as_base()).unwrap());
295 }
296
297 #[test]
298 fn test_indexed_new_load_v0() {
299 let dir = TempDir::new().unwrap();
300 let blob_path = dir.as_path().join("blob-1");
301 let blob_path = blob_path.as_os_str().to_str().unwrap().to_string();
302
303 assert!(IndexedChunkMap::new(&blob_path, 0, true).is_err());
304
305 let cache_path = format!("{}.{}", blob_path, FILE_SUFFIX);
306 let mut file = OpenOptions::new()
307 .read(true)
308 .write(true)
309 .create(true)
310 .truncate(false)
311 .open(&cache_path)
312 .map_err(|err| {
313 einval!(format!(
314 "failed to open/create blob chunk_map file {:?}: {:?}",
315 cache_path, err
316 ))
317 })
318 .unwrap();
319 let header = Header {
320 magic: MAGIC1,
321 version: 0,
322 magic2: 0,
323 all_ready: 0,
324 reserved: [0x0u8; HEADER_RESERVED_SIZE],
325 };
326
327 file.write_all(header.as_slice()).unwrap();
329 file.write_all(&[0x0u8]).unwrap();
330
331 let chunk = MockChunkInfo::new();
332 assert_eq!(chunk.id(), 0);
333
334 let map = IndexedChunkMap::new(&blob_path, 1, true).unwrap();
335 assert_eq!(map.map.not_ready_count.load(Ordering::Acquire), 1);
336 assert_eq!(map.map.count, 1);
337 assert_eq!(map.map.size(), 0x1001);
338 assert!(!map.is_range_all_ready());
339 assert!(!map.is_ready(chunk.as_base()).unwrap());
340 map.set_ready_and_clear_pending(chunk.as_base()).unwrap();
341 assert!(map.is_ready(chunk.as_base()).unwrap());
342 }
343}