1use std::io;
6
7use libc::EINVAL;
8use remain::sorted;
9use thiserror::Error;
10
11use crate::qcow::qcow_raw_file::QcowRawFile;
12use crate::qcow::vec_cache::CacheMap;
13use crate::qcow::vec_cache::Cacheable;
14use crate::qcow::vec_cache::VecCache;
15
16#[sorted]
17#[derive(Error, Debug)]
18pub enum Error {
19 #[error("failed to write a refblock from the cache to disk: {0}")]
21 EvictingRefCounts(io::Error),
22 #[error("address requested is not within the range of the disk")]
24 InvalidIndex,
25 #[error("cluster with addr={0} needs to be read")]
27 NeedCluster(u64),
28 #[error("new cluster needs to be allocated for refcounts")]
31 NeedNewCluster,
32 #[error("failed to read the file into the refcount cache: {0}")]
34 ReadingRefCounts(io::Error),
35}
36
37pub type Result<T> = std::result::Result<T, Error>;
38
39#[derive(Debug)]
41pub struct RefCount {
42 ref_table: VecCache<u64>,
43 refcount_table_offset: u64,
44 refblock_cache: CacheMap<VecCache<u16>>,
45 refcount_block_entries: u64, cluster_size: u64,
47 max_valid_cluster_offset: u64,
48}
49
50impl RefCount {
51 pub fn new(
56 raw_file: &mut QcowRawFile,
57 refcount_table_offset: u64,
58 refcount_table_entries: u64,
59 refcount_block_entries: u64,
60 cluster_size: u64,
61 ) -> io::Result<RefCount> {
62 let ref_table = VecCache::from_vec(raw_file.read_pointer_table(
63 refcount_table_offset,
64 refcount_table_entries,
65 None,
66 )?);
67 let max_valid_cluster_index = (ref_table.len() as u64) * refcount_block_entries - 1;
68 let max_valid_cluster_offset = max_valid_cluster_index * cluster_size;
69 Ok(RefCount {
70 ref_table,
71 refcount_table_offset,
72 refblock_cache: CacheMap::new(50),
73 refcount_block_entries,
74 cluster_size,
75 max_valid_cluster_offset,
76 })
77 }
78
79 pub fn refcounts_per_block(&self) -> u64 {
81 self.refcount_block_entries
82 }
83
84 pub fn max_valid_cluster_offset(&self) -> u64 {
86 self.max_valid_cluster_offset
87 }
88
89 pub fn set_cluster_refcount(
95 &mut self,
96 raw_file: &mut QcowRawFile,
97 cluster_address: u64,
98 refcount: u16,
99 mut new_cluster: Option<(u64, VecCache<u16>)>,
100 ) -> Result<Option<u64>> {
101 let (table_index, block_index) = self.get_refcount_index(cluster_address);
102
103 let block_addr_disk = *self.ref_table.get(table_index).ok_or(Error::InvalidIndex)?;
104
105 if !self.refblock_cache.contains_key(&table_index) {
107 if let Some((addr, table)) = new_cluster.take() {
109 self.ref_table[table_index] = addr;
110 let ref_table = &self.ref_table;
111 self.refblock_cache
112 .insert(table_index, table, |index, evicted| {
113 raw_file.write_refcount_block(ref_table[index], evicted.get_values())
114 })
115 .map_err(Error::EvictingRefCounts)?;
116 } else {
117 if block_addr_disk == 0 {
118 return Err(Error::NeedNewCluster);
119 }
120 return Err(Error::NeedCluster(block_addr_disk));
121 }
122 }
123
124 let dropped_cluster = if !self.refblock_cache.get(&table_index).unwrap().dirty() {
126 if let Some((addr, _)) = new_cluster.take() {
129 self.ref_table[table_index] = addr;
130 Some(block_addr_disk)
131 } else {
132 return Err(Error::NeedNewCluster);
133 }
134 } else {
135 None
136 };
137
138 self.refblock_cache.get_mut(&table_index).unwrap()[block_index] = refcount;
139 Ok(dropped_cluster)
140 }
141
142 pub fn flush_blocks(&mut self, raw_file: &mut QcowRawFile) -> io::Result<()> {
145 for (table_index, block) in self.refblock_cache.iter_mut().filter(|(_k, v)| v.dirty()) {
147 let addr = self.ref_table[*table_index];
148 if addr != 0 {
149 raw_file.write_refcount_block(addr, block.get_values())?;
150 } else {
151 return Err(std::io::Error::from_raw_os_error(EINVAL));
152 }
153 block.mark_clean();
154 }
155 Ok(())
156 }
157
158 pub fn flush_table(&mut self, raw_file: &mut QcowRawFile) -> io::Result<bool> {
161 if self.ref_table.dirty() {
162 raw_file.write_pointer_table(
163 self.refcount_table_offset,
164 self.ref_table.get_values(),
165 0,
166 )?;
167 self.ref_table.mark_clean();
168 Ok(true)
169 } else {
170 Ok(false)
171 }
172 }
173
174 pub fn get_cluster_refcount(
176 &mut self,
177 raw_file: &mut QcowRawFile,
178 address: u64,
179 ) -> Result<u16> {
180 let (table_index, block_index) = self.get_refcount_index(address);
181 let block_addr_disk = *self.ref_table.get(table_index).ok_or(Error::InvalidIndex)?;
182 if block_addr_disk == 0 {
183 return Ok(0);
184 }
185 if !self.refblock_cache.contains_key(&table_index) {
186 let table = VecCache::from_vec(
187 raw_file
188 .read_refcount_block(block_addr_disk)
189 .map_err(Error::ReadingRefCounts)?,
190 );
191 let ref_table = &self.ref_table;
192 self.refblock_cache
193 .insert(table_index, table, |index, evicted| {
194 raw_file.write_refcount_block(ref_table[index], evicted.get_values())
195 })
196 .map_err(Error::EvictingRefCounts)?;
197 }
198 Ok(self.refblock_cache.get(&table_index).unwrap()[block_index])
199 }
200
201 fn get_refcount_index(&self, address: u64) -> (usize, usize) {
203 let block_index = (address / self.cluster_size) % self.refcount_block_entries;
204 let refcount_table_index = (address / self.cluster_size) / self.refcount_block_entries;
205 (refcount_table_index as usize, block_index as usize)
206 }
207}