use std::io::Result;
use crate::cache::state::persist_map::PersistMap;
use crate::cache::state::{ChunkIndexGetter, ChunkMap, RangeMap};
use crate::device::BlobChunkInfo;
const FILE_SUFFIX: &str = "chunk_map";
pub struct IndexedChunkMap {
map: PersistMap,
}
impl IndexedChunkMap {
pub fn new(blob_path: &str, chunk_count: u32, persist: bool) -> Result<Self> {
let filename = format!("{}.{}", blob_path, FILE_SUFFIX);
PersistMap::open(&filename, chunk_count, true, persist).map(|map| IndexedChunkMap { map })
}
}
impl ChunkMap for IndexedChunkMap {
fn is_ready(&self, chunk: &dyn BlobChunkInfo) -> Result<bool> {
if self.is_range_all_ready() {
Ok(true)
} else {
let index = self.map.validate_index(chunk.id())?;
Ok(self.map.is_chunk_ready(index).0)
}
}
fn set_ready_and_clear_pending(&self, chunk: &dyn BlobChunkInfo) -> Result<()> {
self.map.set_chunk_ready(chunk.id())
}
fn is_persist(&self) -> bool {
true
}
fn as_range_map(&self) -> Option<&dyn RangeMap<I = u32>> {
Some(self)
}
}
impl RangeMap for IndexedChunkMap {
type I = u32;
#[inline]
fn is_range_all_ready(&self) -> bool {
self.map.is_range_all_ready()
}
fn is_range_ready(&self, start_index: u32, count: u32) -> Result<bool> {
if !self.is_range_all_ready() {
for idx in 0..count {
let index = self
.map
.validate_index(start_index.checked_add(idx).ok_or_else(|| einval!())?)?;
if !self.map.is_chunk_ready(index).0 {
return Ok(false);
}
}
}
Ok(true)
}
fn check_range_ready_and_mark_pending(
&self,
start_index: u32,
count: u32,
) -> Result<Option<Vec<u32>>> {
if self.is_range_all_ready() {
return Ok(None);
}
let mut vec = Vec::with_capacity(count as usize);
let count = std::cmp::min(count, u32::MAX - start_index);
let end = start_index + count;
for index in start_index..end {
if !self.map.is_chunk_ready(index).0 {
vec.push(index);
}
}
if vec.is_empty() {
Ok(None)
} else {
Ok(Some(vec))
}
}
fn set_range_ready_and_clear_pending(&self, start_index: u32, count: u32) -> Result<()> {
let count = std::cmp::min(count, u32::MAX - start_index);
let end = start_index + count;
for index in start_index..end {
self.map.set_chunk_ready(index)?;
}
Ok(())
}
}
impl ChunkIndexGetter for IndexedChunkMap {
type Index = u32;
fn get_index(chunk: &dyn BlobChunkInfo) -> Self::Index {
chunk.id()
}
}
#[cfg(test)]
mod tests {
use std::fs::OpenOptions;
use std::io::Write;
use std::sync::atomic::Ordering;
use vmm_sys_util::tempdir::TempDir;
use super::super::persist_map::*;
use super::*;
use crate::device::v5::BlobV5ChunkInfo;
use crate::test::MockChunkInfo;
#[test]
fn test_indexed_new_invalid_file_size() {
let dir = TempDir::new().unwrap();
let blob_path = dir.as_path().join("blob-1");
let blob_path = blob_path.as_os_str().to_str().unwrap().to_string();
assert!(IndexedChunkMap::new(&blob_path, 0, false).is_err());
let cache_path = format!("{}.{}", blob_path, FILE_SUFFIX);
let mut file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(false)
.open(&cache_path)
.map_err(|err| {
einval!(format!(
"failed to open/create blob chunk_map file {:?}: {:?}",
cache_path, err
))
})
.unwrap();
file.write_all(&[0x0u8]).unwrap();
let chunk = MockChunkInfo::new();
assert_eq!(chunk.id(), 0);
assert!(IndexedChunkMap::new(&blob_path, 1, true).is_err());
}
#[test]
fn test_indexed_new_zero_file_size() {
let dir = TempDir::new().unwrap();
let blob_path = dir.as_path().join("blob-1");
let blob_path = blob_path.as_os_str().to_str().unwrap().to_string();
assert!(IndexedChunkMap::new(&blob_path, 0, true).is_err());
let cache_path = format!("{}.{}", blob_path, FILE_SUFFIX);
let _file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(false)
.open(&cache_path)
.map_err(|err| {
einval!(format!(
"failed to open/create blob chunk_map file {:?}: {:?}",
cache_path, err
))
})
.unwrap();
let chunk = MockChunkInfo::new();
assert_eq!(chunk.id(), 0);
let map = IndexedChunkMap::new(&blob_path, 1, true).unwrap();
assert_eq!(map.map.not_ready_count.load(Ordering::Acquire), 1);
assert_eq!(map.map.count, 1);
assert_eq!(map.map.size(), 0x1001);
assert!(!map.is_range_all_ready());
assert!(!map.is_ready(chunk.as_base()).unwrap());
map.set_ready_and_clear_pending(chunk.as_base()).unwrap();
assert!(map.is_ready(chunk.as_base()).unwrap());
}
#[test]
fn test_indexed_new_header_not_ready() {
let dir = TempDir::new().unwrap();
let blob_path = dir.as_path().join("blob-1");
let blob_path = blob_path.as_os_str().to_str().unwrap().to_string();
assert!(IndexedChunkMap::new(&blob_path, 0, true).is_err());
let cache_path = format!("{}.{}", blob_path, FILE_SUFFIX);
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(false)
.open(&cache_path)
.map_err(|err| {
einval!(format!(
"failed to open/create blob chunk_map file {:?}: {:?}",
cache_path, err
))
})
.unwrap();
file.set_len(0x1001).unwrap();
let chunk = MockChunkInfo::new();
assert_eq!(chunk.id(), 0);
let map = IndexedChunkMap::new(&blob_path, 1, true).unwrap();
assert_eq!(map.map.not_ready_count.load(Ordering::Acquire), 1);
assert_eq!(map.map.count, 1);
assert_eq!(map.map.size(), 0x1001);
assert!(!map.is_range_all_ready());
assert!(!map.is_ready(chunk.as_base()).unwrap());
map.set_ready_and_clear_pending(chunk.as_base()).unwrap();
assert!(map.is_ready(chunk.as_base()).unwrap());
}
#[test]
fn test_indexed_new_all_ready() {
let dir = TempDir::new().unwrap();
let blob_path = dir.as_path().join("blob-1");
let blob_path = blob_path.as_os_str().to_str().unwrap().to_string();
assert!(IndexedChunkMap::new(&blob_path, 0, true).is_err());
let cache_path = format!("{}.{}", blob_path, FILE_SUFFIX);
let mut file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(false)
.open(&cache_path)
.map_err(|err| {
einval!(format!(
"failed to open/create blob chunk_map file {:?}: {:?}",
cache_path, err
))
})
.unwrap();
let header = Header {
magic: MAGIC1,
version: 1,
magic2: MAGIC2,
all_ready: MAGIC_ALL_READY,
reserved: [0x0u8; HEADER_RESERVED_SIZE],
};
file.write_all(header.as_slice()).unwrap();
file.write_all(&[0x0u8]).unwrap();
let chunk = MockChunkInfo::new();
assert_eq!(chunk.id(), 0);
let map = IndexedChunkMap::new(&blob_path, 1, true).unwrap();
assert!(map.is_range_all_ready());
assert_eq!(map.map.count, 1);
assert_eq!(map.map.size(), 0x1001);
assert!(map.is_ready(chunk.as_base()).unwrap());
map.set_ready_and_clear_pending(chunk.as_base()).unwrap();
assert!(map.is_ready(chunk.as_base()).unwrap());
}
#[test]
fn test_indexed_new_load_v0() {
let dir = TempDir::new().unwrap();
let blob_path = dir.as_path().join("blob-1");
let blob_path = blob_path.as_os_str().to_str().unwrap().to_string();
assert!(IndexedChunkMap::new(&blob_path, 0, true).is_err());
let cache_path = format!("{}.{}", blob_path, FILE_SUFFIX);
let mut file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(false)
.open(&cache_path)
.map_err(|err| {
einval!(format!(
"failed to open/create blob chunk_map file {:?}: {:?}",
cache_path, err
))
})
.unwrap();
let header = Header {
magic: MAGIC1,
version: 0,
magic2: 0,
all_ready: 0,
reserved: [0x0u8; HEADER_RESERVED_SIZE],
};
file.write_all(header.as_slice()).unwrap();
file.write_all(&[0x0u8]).unwrap();
let chunk = MockChunkInfo::new();
assert_eq!(chunk.id(), 0);
let map = IndexedChunkMap::new(&blob_path, 1, true).unwrap();
assert_eq!(map.map.not_ready_count.load(Ordering::Acquire), 1);
assert_eq!(map.map.count, 1);
assert_eq!(map.map.size(), 0x1001);
assert!(!map.is_range_all_ready());
assert!(!map.is_ready(chunk.as_base()).unwrap());
map.set_ready_and_clear_pending(chunk.as_base()).unwrap();
assert!(map.is_ready(chunk.as_base()).unwrap());
}
}