Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
google
GitHub Repository: google/crosvm
Path: blob/main/disk/src/qcow/refcount.rs
5394 views
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.
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.
80
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.
85
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.
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.
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.
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.
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.
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
209