• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 again.
29     #[error("new cluster needs to be allocated for refcounts")]
30     NeedNewCluster,
31     /// `ReadingRefCounts` - Error reading the file in to the refcount cache.
32     #[error("failed to read the file into the refcount cache: {0}")]
33     ReadingRefCounts(io::Error),
34 }
35 
36 pub type Result<T> = std::result::Result<T, Error>;
37 
38 /// Represents the refcount entries for an open qcow file.
39 #[derive(Debug)]
40 pub struct RefCount {
41     ref_table: VecCache<u64>,
42     refcount_table_offset: u64,
43     refblock_cache: CacheMap<VecCache<u16>>,
44     refcount_block_entries: u64, // number of refcounts in a cluster.
45     cluster_size: u64,
46     max_valid_cluster_offset: u64,
47 }
48 
49 impl RefCount {
50     /// Creates a `RefCount` from `file`, reading the refcount table from `refcount_table_offset`.
51     /// `refcount_table_entries` specifies the number of refcount blocks used by this image.
52     /// `refcount_block_entries` indicates the number of refcounts in each refcount block.
53     /// 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>54     pub fn new(
55         raw_file: &mut QcowRawFile,
56         refcount_table_offset: u64,
57         refcount_table_entries: u64,
58         refcount_block_entries: u64,
59         cluster_size: u64,
60     ) -> io::Result<RefCount> {
61         let ref_table = VecCache::from_vec(raw_file.read_pointer_table(
62             refcount_table_offset,
63             refcount_table_entries,
64             None,
65         )?);
66         let max_valid_cluster_index = (ref_table.len() as u64) * refcount_block_entries - 1;
67         let max_valid_cluster_offset = max_valid_cluster_index * cluster_size;
68         Ok(RefCount {
69             ref_table,
70             refcount_table_offset,
71             refblock_cache: CacheMap::new(50),
72             refcount_block_entries,
73             cluster_size,
74             max_valid_cluster_offset,
75         })
76     }
77 
78     /// Returns the number of refcounts per block.
refcounts_per_block(&self) -> u6479     pub fn refcounts_per_block(&self) -> u64 {
80         self.refcount_block_entries
81     }
82 
83     /// Returns the maximum valid cluster offset in the raw file for this refcount table.
max_valid_cluster_offset(&self) -> u6484     pub fn max_valid_cluster_offset(&self) -> u64 {
85         self.max_valid_cluster_offset
86     }
87 
88     /// Returns `NeedNewCluster` if a new cluster needs to be allocated for refcounts. If an
89     /// existing cluster needs to be read, `NeedCluster(addr)` is returned. The Caller should
90     /// allocate a cluster or read the required one and call this function again with the cluster.
91     /// On success, an optional address of a dropped cluster is returned. The dropped cluster can
92     /// 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>>93     pub fn set_cluster_refcount(
94         &mut self,
95         raw_file: &mut QcowRawFile,
96         cluster_address: u64,
97         refcount: u16,
98         mut new_cluster: Option<(u64, VecCache<u16>)>,
99     ) -> Result<Option<u64>> {
100         let (table_index, block_index) = self.get_refcount_index(cluster_address);
101 
102         let block_addr_disk = *self.ref_table.get(table_index).ok_or(Error::InvalidIndex)?;
103 
104         // Fill the cache if this block isn't yet there.
105         if !self.refblock_cache.contains_key(&table_index) {
106             // Need a new cluster
107             if let Some((addr, table)) = new_cluster.take() {
108                 self.ref_table[table_index] = addr;
109                 let ref_table = &self.ref_table;
110                 self.refblock_cache
111                     .insert(table_index, table, |index, evicted| {
112                         raw_file.write_refcount_block(ref_table[index], evicted.get_values())
113                     })
114                     .map_err(Error::EvictingRefCounts)?;
115             } else {
116                 if block_addr_disk == 0 {
117                     return Err(Error::NeedNewCluster);
118                 }
119                 return Err(Error::NeedCluster(block_addr_disk));
120             }
121         }
122 
123         // Unwrap is safe here as the entry was filled directly above.
124         let dropped_cluster = if !self.refblock_cache.get(&table_index).unwrap().dirty() {
125             // Free the previously used block and use a new one. Writing modified counts to new
126             // blocks keeps the on-disk state consistent even if it's out of date.
127             if let Some((addr, _)) = new_cluster.take() {
128                 self.ref_table[table_index] = addr;
129                 Some(block_addr_disk)
130             } else {
131                 return Err(Error::NeedNewCluster);
132             }
133         } else {
134             None
135         };
136 
137         self.refblock_cache.get_mut(&table_index).unwrap()[block_index] = refcount;
138         Ok(dropped_cluster)
139     }
140 
141     /// Flush the dirty refcount blocks. This must be done before flushing the table that points to
142     /// the blocks.
flush_blocks(&mut self, raw_file: &mut QcowRawFile) -> io::Result<()>143     pub fn flush_blocks(&mut self, raw_file: &mut QcowRawFile) -> io::Result<()> {
144         // Write out all dirty L2 tables.
145         for (table_index, block) in self.refblock_cache.iter_mut().filter(|(_k, v)| v.dirty()) {
146             let addr = self.ref_table[*table_index];
147             if addr != 0 {
148                 raw_file.write_refcount_block(addr, block.get_values())?;
149             } else {
150                 return Err(std::io::Error::from_raw_os_error(EINVAL));
151             }
152             block.mark_clean();
153         }
154         Ok(())
155     }
156 
157     /// Flush the refcount table that keeps the address of the refcounts blocks.
158     /// Returns true if the table changed since the previous `flush_table()` call.
flush_table(&mut self, raw_file: &mut QcowRawFile) -> io::Result<bool>159     pub fn flush_table(&mut self, raw_file: &mut QcowRawFile) -> io::Result<bool> {
160         if self.ref_table.dirty() {
161             raw_file.write_pointer_table(
162                 self.refcount_table_offset,
163                 self.ref_table.get_values(),
164                 0,
165             )?;
166             self.ref_table.mark_clean();
167             Ok(true)
168         } else {
169             Ok(false)
170         }
171     }
172 
173     /// Gets the refcount for a cluster with the given address.
get_cluster_refcount( &mut self, raw_file: &mut QcowRawFile, address: u64, ) -> Result<u16>174     pub fn get_cluster_refcount(
175         &mut self,
176         raw_file: &mut QcowRawFile,
177         address: u64,
178     ) -> Result<u16> {
179         let (table_index, block_index) = self.get_refcount_index(address);
180         let block_addr_disk = *self.ref_table.get(table_index).ok_or(Error::InvalidIndex)?;
181         if block_addr_disk == 0 {
182             return Ok(0);
183         }
184         if !self.refblock_cache.contains_key(&table_index) {
185             let table = VecCache::from_vec(
186                 raw_file
187                     .read_refcount_block(block_addr_disk)
188                     .map_err(Error::ReadingRefCounts)?,
189             );
190             let ref_table = &self.ref_table;
191             self.refblock_cache
192                 .insert(table_index, table, |index, evicted| {
193                     raw_file.write_refcount_block(ref_table[index], evicted.get_values())
194                 })
195                 .map_err(Error::EvictingRefCounts)?;
196         }
197         Ok(self.refblock_cache.get(&table_index).unwrap()[block_index])
198     }
199 
200     // 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)201     fn get_refcount_index(&self, address: u64) -> (usize, usize) {
202         let block_index = (address / self.cluster_size) % self.refcount_block_entries;
203         let refcount_table_index = (address / self.cluster_size) / self.refcount_block_entries;
204         (refcount_table_index as usize, block_index as usize)
205     }
206 }
207