Skip to main content

imago/qcow2/
cow.rs

1//! Copy-on-write operations.
2//!
3//! Implements copy-on-write when writing to clusters that are not simple allocated data clusters.
4
5use super::*;
6use crate::io_buffers::IoBuffer;
7
8#[maybe_async]
9impl<S: Storage, F: WrappedFormat<S>> Qcow2<S, F> {
10    /// Do copy-on-write for the given guest cluster, if necessary.
11    ///
12    /// If the given guest cluster is backed by an allocated copied data cluster, return that
13    /// cluster, so it can just be written into.
14    ///
15    /// Otherwise, allocate a new data cluster and copy the previously visible cluster contents
16    /// there:
17    /// - For non-copied data clusters, copy the cluster contents.
18    /// - For zero clusters, write zeroes.
19    /// - For unallocated clusters, copy data from the backing file (if any, zeroes otherwise).
20    /// - For compressed clusters, decompress the data and write it into the new cluster.
21    ///
22    /// Return the new cluster, if any was allocated, or the old cluster in case it was already
23    /// safe to write to.  I.e., the returned cluster is where data for `cluster` may be written
24    /// to.
25    ///
26    /// `cluster` is the guest cluster to COW.
27    ///
28    /// `mandatory_host_cluster` may specify the cluster that must be used for the new allocation,
29    /// or that an existing data cluster allocation must match.  If it does not match, or that
30    /// cluster is already allocated and cannot be used, return `Ok(None)`.
31    ///
32    /// `partial_skip_cow` may give an in-cluster range that is supposed to be overwritten
33    /// immediately anyway, i.e. that need not be copied.
34    ///
35    /// `l2_table` is the L2 table for `offset`.
36    ///
37    /// If a previously existing allocation is replaced, the old one will be put into
38    /// `leaked_allocations`.  The caller must free it.
39    pub(super) async fn cow_cluster(
40        &self,
41        cluster: GuestCluster,
42        mandatory_host_cluster: Option<HostCluster>,
43        partial_skip_cow: Option<Range<usize>>,
44        l2_table: &mut L2TableWriteGuard<'_>,
45        leaked_allocations: &mut Vec<(HostCluster, ClusterCount)>,
46    ) -> io::Result<Option<HostCluster>> {
47        // No need to do COW when writing the full cluster
48        let full_skip_cow = if let Some(skip) = partial_skip_cow.as_ref() {
49            skip.start == 0 && skip.end == self.header.cluster_size()
50        } else {
51            false
52        };
53
54        let existing_mapping = l2_table.get_mapping(cluster)?;
55        if let L2Mapping::DataFile {
56            host_cluster,
57            copied: true,
58        } = existing_mapping
59        {
60            if let Some(mandatory_host_cluster) = mandatory_host_cluster {
61                if host_cluster != mandatory_host_cluster {
62                    return Ok(None);
63                }
64            }
65            return Ok(Some(host_cluster));
66        };
67
68        self.need_writable()?;
69
70        let new_cluster = if let L2Mapping::Zero {
71            host_cluster: Some(host_cluster),
72            copied: true,
73        } = existing_mapping
74        {
75            if let Some(mandatory_host_cluster) = mandatory_host_cluster {
76                if host_cluster == mandatory_host_cluster {
77                    Some(host_cluster)
78                } else {
79                    // Discard existing mapping
80                    self.allocate_data_cluster_at(cluster, Some(mandatory_host_cluster))
81                        .await?
82                }
83            } else {
84                Some(host_cluster)
85            }
86        } else {
87            self.allocate_data_cluster_at(cluster, mandatory_host_cluster)
88                .await?
89        };
90        let Some(new_cluster) = new_cluster else {
91            // Allocation at `mandatory_host_cluster` failed
92            return Ok(None);
93        };
94
95        if !full_skip_cow {
96            match existing_mapping {
97                L2Mapping::DataFile {
98                    host_cluster: _,
99                    copied: true,
100                } => unreachable!(),
101
102                L2Mapping::DataFile {
103                    host_cluster,
104                    copied: false,
105                } => {
106                    self.cow_copy_storage(
107                        self.storage(),
108                        host_cluster,
109                        new_cluster,
110                        partial_skip_cow,
111                    )
112                    .await?
113                }
114
115                L2Mapping::Backing { backing_offset } => {
116                    if let Some(backing) = self.backing.as_ref() {
117                        self.cow_copy_format(backing, backing_offset, new_cluster, partial_skip_cow)
118                            .await?
119                    } else {
120                        self.cow_zero(new_cluster, partial_skip_cow).await?
121                    }
122                }
123
124                L2Mapping::Zero {
125                    host_cluster: _,
126                    copied: _,
127                } => self.cow_zero(new_cluster, partial_skip_cow).await?,
128
129                L2Mapping::Compressed {
130                    host_offset,
131                    length,
132                } => {
133                    self.cow_compressed(host_offset, length, new_cluster)
134                        .await?
135                }
136            }
137        }
138
139        let l2i = cluster.l2_index(self.header.cluster_bits());
140        if let Some(leaked) = l2_table.map_cluster(l2i, new_cluster) {
141            leaked_allocations.push(leaked);
142        }
143
144        Ok(Some(new_cluster))
145    }
146
147    /// Calculate what range of a cluster we need to COW.
148    ///
149    /// Given potentially a range to skip, calculate what we should COW.  The range will only be
150    /// taken into account if it is at one end of the cluster, to always yield a continuous range
151    /// to COW (one without a hole in the middle).
152    ///
153    /// The returned range is also aligned to `alignment` if possible.
154    fn get_cow_range(
155        &self,
156        partial_skip_cow: Option<Range<usize>>,
157        alignment: usize,
158    ) -> Option<Range<usize>> {
159        let mut copy_range = 0..self.header.cluster_size();
160        if let Some(partial_skip_cow) = partial_skip_cow {
161            if partial_skip_cow.start == copy_range.start {
162                copy_range.start = partial_skip_cow.end;
163            } else if partial_skip_cow.end == copy_range.end {
164                copy_range.end = partial_skip_cow.start;
165            }
166        }
167
168        if copy_range.is_empty() {
169            return None;
170        }
171
172        let alignment = cmp::min(alignment, self.header.cluster_size());
173        debug_assert!(alignment.is_power_of_two());
174        let mask = alignment - 1;
175
176        if copy_range.start & mask != 0 {
177            copy_range.start &= !mask;
178        }
179        if copy_range.end & mask != 0 {
180            copy_range.end = (copy_range.end & !mask) + alignment;
181        }
182
183        Some(copy_range)
184    }
185
186    /// Copy data from one data file cluster to another.
187    ///
188    /// Used for COW on non-copied data clusters.
189    async fn cow_copy_storage(
190        &self,
191        from: &S,
192        from_cluster: HostCluster,
193        to_cluster: HostCluster,
194        partial_skip_cow: Option<Range<usize>>,
195    ) -> io::Result<()> {
196        let to = self.storage();
197
198        let align = cmp::max(from.req_align(), to.req_align());
199        let Some(cow_range) = self.get_cow_range(partial_skip_cow, align) else {
200            return Ok(());
201        };
202
203        let mut buf = IoBuffer::new(cow_range.end - cow_range.start, from.mem_align())?;
204
205        let cb = self.header.cluster_bits();
206        let from_offset = from_cluster.offset(cb);
207        let to_offset = to_cluster.offset(cb);
208
209        from.read(&mut buf, from_offset.0 + cow_range.start as u64)
210            .await?;
211
212        to.write(&buf, to_offset.0 + cow_range.start as u64).await?;
213
214        Ok(())
215    }
216
217    /// Copy data from another image into our data file.
218    ///
219    /// Used for COW on clusters served by a backing image.
220    async fn cow_copy_format(
221        &self,
222        from: &F,
223        from_offset: u64,
224        to_cluster: HostCluster,
225        partial_skip_cow: Option<Range<usize>>,
226    ) -> io::Result<()> {
227        let to = self.storage();
228        let from = from.inner();
229
230        let align = cmp::max(from.req_align(), to.req_align());
231        let Some(cow_range) = self.get_cow_range(partial_skip_cow, align) else {
232            return Ok(());
233        };
234
235        let mut buf = IoBuffer::new(cow_range.end - cow_range.start, from.mem_align())?;
236
237        let to_offset = to_cluster.offset(self.header.cluster_bits());
238
239        from.read(&mut buf, from_offset + cow_range.start as u64)
240            .await?;
241
242        to.write(&buf, to_offset.0 + cow_range.start as u64).await?;
243
244        Ok(())
245    }
246
247    /// Fill the given cluster with zeroes.
248    ///
249    /// Used for COW on zero clusters.
250    async fn cow_zero(
251        &self,
252        to_cluster: HostCluster,
253        partial_skip_cow: Option<Range<usize>>,
254    ) -> io::Result<()> {
255        let to = self.storage();
256
257        let align = to.req_align();
258        let Some(cow_range) = self.get_cow_range(partial_skip_cow, align) else {
259            return Ok(());
260        };
261
262        let to_offset = to_cluster.offset(self.header.cluster_bits());
263        to.write_zeroes(
264            to_offset.0 + cow_range.start as u64,
265            (cow_range.end - cow_range.start) as u64,
266        )
267        .await?;
268
269        Ok(())
270    }
271
272    /// Decompress a cluster into the target cluster.
273    ///
274    /// Used for COW on compressed clusters.
275    async fn cow_compressed(
276        &self,
277        compressed_offset: HostOffset,
278        compressed_length: u64,
279        to_cluster: HostCluster,
280    ) -> io::Result<()> {
281        let to = self.storage();
282
283        let mut buf = IoBuffer::new(self.header.cluster_size(), to.mem_align())?;
284        self.read_compressed_cluster(
285            buf.as_mut().into_slice(),
286            compressed_offset,
287            compressed_length,
288        )
289        .await?;
290
291        let to_offset = to_cluster.offset(self.header.cluster_bits());
292        to.write(&buf, to_offset.0).await?;
293
294        Ok(())
295    }
296}