1 // Copyright 2018 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 5 use std::io; 6 7 use libc::EINVAL; 8 use remain::sorted; 9 use thiserror::Error; 10 11 use crate::qcow::qcow_raw_file::QcowRawFile; 12 use crate::qcow::vec_cache::CacheMap; 13 use crate::qcow::vec_cache::Cacheable; 14 use crate::qcow::vec_cache::VecCache; 15 16 #[sorted] 17 #[derive(Error, Debug)] 18 pub enum Error { 19 /// `EvictingCache` - Error writing a refblock from the cache to disk. 20 #[error("failed to write a refblock from the cache to disk: {0}")] 21 EvictingRefCounts(io::Error), 22 /// `InvalidIndex` - Address requested isn't within the range of the disk. 23 #[error("address requested is not within the range of the disk")] 24 InvalidIndex, 25 /// `NeedCluster` - Handle this error by reading the cluster and calling the function again. 26 #[error("cluster with addr={0} needs to be read")] 27 NeedCluster(u64), 28 /// `NeedNewCluster` - Handle this error by allocating a cluster and calling the function 29 /// again. 30 #[error("new cluster needs to be allocated for refcounts")] 31 NeedNewCluster, 32 /// `ReadingRefCounts` - Error reading the file in to the refcount cache. 33 #[error("failed to read the file into the refcount cache: {0}")] 34 ReadingRefCounts(io::Error), 35 } 36 37 pub type Result<T> = std::result::Result<T, Error>; 38 39 /// Represents the refcount entries for an open qcow file. 40 #[derive(Debug)] 41 pub struct RefCount { 42 ref_table: VecCache<u64>, 43 refcount_table_offset: u64, 44 refblock_cache: CacheMap<VecCache<u16>>, 45 refcount_block_entries: u64, // number of refcounts in a cluster. 46 cluster_size: u64, 47 max_valid_cluster_offset: u64, 48 } 49 50 impl RefCount { 51 /// Creates a `RefCount` from `file`, reading the refcount table from `refcount_table_offset`. 52 /// `refcount_table_entries` specifies the number of refcount blocks used by this image. 53 /// `refcount_block_entries` indicates the number of refcounts in each refcount block. 54 /// Each refcount table entry points to a refcount block. new( raw_file: &mut QcowRawFile, refcount_table_offset: u64, refcount_table_entries: u64, refcount_block_entries: u64, cluster_size: u64, ) -> io::Result<RefCount>55 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 /// Returns the number of refcounts per block. refcounts_per_block(&self) -> u6480 pub fn refcounts_per_block(&self) -> u64 { 81 self.refcount_block_entries 82 } 83 84 /// Returns the maximum valid cluster offset in the raw file for this refcount table. max_valid_cluster_offset(&self) -> u6485 pub fn max_valid_cluster_offset(&self) -> u64 { 86 self.max_valid_cluster_offset 87 } 88 89 /// Returns `NeedNewCluster` if a new cluster needs to be allocated for refcounts. If an 90 /// existing cluster needs to be read, `NeedCluster(addr)` is returned. The Caller should 91 /// allocate a cluster or read the required one and call this function again with the cluster. 92 /// On success, an optional address of a dropped cluster is returned. The dropped cluster can 93 /// be reused for other purposes. set_cluster_refcount( &mut self, raw_file: &mut QcowRawFile, cluster_address: u64, refcount: u16, mut new_cluster: Option<(u64, VecCache<u16>)>, ) -> Result<Option<u64>>94 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 // Fill the cache if this block isn't yet there. 106 if !self.refblock_cache.contains_key(&table_index) { 107 // Need a new cluster 108 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 // Unwrap is safe here as the entry was filled directly above. 125 let dropped_cluster = if !self.refblock_cache.get(&table_index).unwrap().dirty() { 126 // Free the previously used block and use a new one. Writing modified counts to new 127 // blocks keeps the on-disk state consistent even if it's out of date. 128 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 /// Flush the dirty refcount blocks. This must be done before flushing the table that points to 143 /// the blocks. flush_blocks(&mut self, raw_file: &mut QcowRawFile) -> io::Result<()>144 pub fn flush_blocks(&mut self, raw_file: &mut QcowRawFile) -> io::Result<()> { 145 // Write out all dirty L2 tables. 146 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 /// Flush the refcount table that keeps the address of the refcounts blocks. 159 /// Returns true if the table changed since the previous `flush_table()` call. flush_table(&mut self, raw_file: &mut QcowRawFile) -> io::Result<bool>160 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 /// Gets the refcount for a cluster with the given address. get_cluster_refcount( &mut self, raw_file: &mut QcowRawFile, address: u64, ) -> Result<u16>175 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 // Gets the address of the refcount block and the index into the block for the given address. get_refcount_index(&self, address: u64) -> (usize, usize)202 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 } 208