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