Skip to main content

imago/qcow2/
allocation.rs

1//! Cluster allocation.
2//!
3//! Functionality for allocating single clusters and ranges of clusters, and general handling of
4//! refcount structures.
5
6use super::*;
7use crate::sync_primitives::MutexGuard;
8use std::mem;
9use tracing::{event, warn, Level};
10
11/// Central facility for cluster allocation.
12pub(super) struct Allocator<S: Storage> {
13    /// Qcow2 metadata file.
14    file: Arc<S>,
15
16    /// Qcow2 refcount table.
17    reftable: RefTable,
18
19    /// The first free cluster index in the qcow2 file, to speed up allocation.
20    first_free_cluster: HostCluster,
21
22    /// Qcow2 image header.
23    header: Arc<Header>,
24
25    /// L2 and refblock caches with dependency management.
26    caches: Arc<MetadataCaches<S>>,
27}
28
29#[maybe_async]
30impl<S: Storage + 'static, F: WrappedFormat<S> + 'static> Qcow2<S, F> {
31    /// Return the central allocator instance.
32    ///
33    /// Returns an error for read-only images.
34    async fn allocator(&self) -> io::Result<MutexGuard<'_, Allocator<S>>> {
35        Ok(self
36            .allocator
37            .as_ref()
38            .ok_or_else(|| io::Error::other("Image is read-only"))?
39            .lock()
40            .await)
41    }
42
43    /// Allocate one metadata cluster.
44    ///
45    /// Metadata clusters are allocated exclusively in the metadata (image) file.
46    pub(super) async fn allocate_meta_cluster(&self) -> io::Result<HostCluster> {
47        self.allocate_meta_clusters(ClusterCount(1)).await
48    }
49
50    /// Allocate multiple continuous metadata clusters.
51    ///
52    /// Useful e.g. for the L1 table or refcount table.
53    pub(super) async fn allocate_meta_clusters(
54        &self,
55        count: ClusterCount,
56    ) -> io::Result<HostCluster> {
57        self.allocator().await?.allocate_clusters(count, None).await
58    }
59
60    /// Allocate one data clusters for the given guest cluster.
61    ///
62    /// Without an external data file, data clusters are allocated in the image file, just like
63    /// metadata clusters.
64    ///
65    /// With an external data file, data clusters aren’t really allocated, but just put there at
66    /// the same offset as their guest offset.  Their refcount is not tracked by the qcow2 metadata
67    /// structures (which only cover the metadata (image) file).
68    pub(super) async fn allocate_data_cluster(
69        &self,
70        guest_cluster: GuestCluster,
71    ) -> io::Result<HostCluster> {
72        if self.header.external_data_file() {
73            Ok(HostCluster(guest_cluster.0))
74        } else {
75            let mut allocator = self.allocator().await?;
76
77            // Allocate clusters before setting up L2 entries
78            self.caches.l2_depends_on_rb().await?;
79
80            allocator.allocate_clusters(ClusterCount(1), None).await
81        }
82    }
83
84    /// Allocate the data cluster with the given index.
85    ///
86    /// Without a `mandatory_host_cluster` given, this is the same as
87    /// [`Qcow2::allocate_data_cluster()`].
88    ///
89    /// With a `mandatory_host_cluster` given, try to allocate that cluster.  If that is not
90    /// possible because it is already allocated, return `Ok(None)`.
91    pub(super) async fn allocate_data_cluster_at(
92        &self,
93        guest_cluster: GuestCluster,
94        mandatory_host_cluster: Option<HostCluster>,
95    ) -> io::Result<Option<HostCluster>> {
96        let Some(mandatory_host_cluster) = mandatory_host_cluster else {
97            return self.allocate_data_cluster(guest_cluster).await.map(Some);
98        };
99
100        if self.header.external_data_file() {
101            let cluster = HostCluster(guest_cluster.0);
102            Ok((cluster == mandatory_host_cluster).then_some(cluster))
103        } else {
104            let mut allocator = self.allocator().await?;
105
106            // Allocate clusters before setting up L2 entries
107            self.caches.l2_depends_on_rb().await?;
108
109            let cluster = allocator
110                .allocate_cluster_at(mandatory_host_cluster)
111                .await?
112                .then_some(mandatory_host_cluster);
113            Ok(cluster)
114        }
115    }
116
117    /// Free metadata clusters (i.e. decrement their refcount).
118    ///
119    /// Best-effort operation.  On error, the given clusters may be leaked, but no errors are ever
120    /// returned (because there is no good way to handle such errors anyway).
121    pub(super) async fn free_meta_clusters(&self, cluster: HostCluster, count: ClusterCount) {
122        if let Ok(mut allocator) = self.allocator().await {
123            allocator.free_clusters(cluster, count).await
124        }
125    }
126
127    /// Free data clusters (i.e. decrement their refcount).
128    ///
129    /// Best-effort operation.  On error, the given clusters may be leaked, but no errors are ever
130    /// returned (because there is no good way to handle such errors anyway).
131    pub(super) async fn free_data_clusters(&self, cluster: HostCluster, count: ClusterCount) {
132        if !self.header.external_data_file() {
133            if let Ok(mut allocator) = self.allocator().await {
134                // Clear L2 entries before deallocating cluster
135                if let Err(err) = self.caches.rb_depends_on_l2().await {
136                    warn!("Leaking clusters; cannot set up cache dependency: {err}");
137                    return;
138                }
139
140                allocator.free_clusters(cluster, count).await;
141            }
142        }
143    }
144}
145
146#[maybe_async]
147impl<S: Storage> Allocator<S> {
148    /// Create a new allocator for the given image file.
149    pub async fn new(
150        image: Arc<S>,
151        header: Arc<Header>,
152        caches: Arc<MetadataCaches<S>>,
153    ) -> io::Result<Self> {
154        let cb = header.cluster_bits();
155        let rt_offset = header.reftable_offset();
156        let rt_cluster = rt_offset
157            .checked_cluster(cb)
158            .ok_or_else(|| invalid_data(format!("Unaligned refcount table: {rt_offset}")))?;
159
160        let reftable = RefTable::load(
161            image.as_ref(),
162            &header,
163            rt_cluster,
164            header.reftable_entries(),
165        )
166        .await?;
167
168        Ok(Allocator {
169            file: image,
170            reftable,
171            first_free_cluster: HostCluster(0),
172            header,
173            caches,
174        })
175    }
176
177    /// Invaidate the refcount block cache.
178    ///
179    /// # Safety
180    /// May cause image corruption, you must guarantee the on-disk state is consistent.
181    pub async unsafe fn invalidate_rb_cache(&self) -> io::Result<()> {
182        // Safe: Caller says so.
183        unsafe { self.caches.invalidate_rb() }.await
184    }
185
186    /// Allocate clusters in the image file.
187    ///
188    /// `end_cluster` should only be used when allocating refblocks.  When reaching this cluster
189    /// index, abort trying to allocate.  (This is used for allocating refblocks, to prevent
190    /// infinite recursion and speed things up.)
191    async fn allocate_clusters(
192        &mut self,
193        count: ClusterCount,
194        end_cluster: Option<HostCluster>,
195    ) -> io::Result<HostCluster> {
196        let mut index = self.first_free_cluster;
197        loop {
198            if end_cluster == Some(index) {
199                return Err(io::Error::other("Maximum cluster index reached"));
200            }
201
202            let alloc_count = self.allocate_clusters_at(index, count).await?;
203            if alloc_count == count {
204                return Ok(index);
205            }
206
207            index += alloc_count + ClusterCount(1);
208            if index.offset(self.header.cluster_bits()) > MAX_OFFSET {
209                return Err(io::Error::other("Cannot grow qcow2 file any further"));
210            }
211        }
212    }
213
214    /// Allocate the given clusters in the image file.
215    ///
216    /// Allocate up to `count` unallocated clusters starting from `index`.  When encountering an
217    /// already allocated cluster (or any other error), stop, and free the clusters that were just
218    /// newly allocated.
219    ///
220    /// Returns the number of clusters that could be allocated (starting from `index`), which may
221    /// be 0 if `index` has already been allocated.  Note again that in case this is less than
222    /// `count`, those clusters will have been freed again already, so this is just a hint to
223    /// callers that the cluster at `index + count` is already allocated.
224    async fn allocate_clusters_at(
225        &mut self,
226        mut index: HostCluster,
227        mut count: ClusterCount,
228    ) -> io::Result<ClusterCount> {
229        let start_index = index;
230
231        while count > ClusterCount(0) {
232            // Note that `ensure_rb()` in `allocate_cluster_at()` may allocate clusters (new
233            // refblocks), and also a new refcount table.  This can interfere with us allocating a
234            // large continuous region like so (A is our allocation, R is a refblock, imagine a
235            // refblock covers four clusters):
236            //
237            // |AAAA| -- allocated four clusters need new refblock
238            // |AAAA|R   | -- made refblock self-describing, but now allocation cannot go on
239            //
240            // This gets resolved by us retrying, and future refblocks using the region that has
241            // now become free but already has refblocks to cover it:
242            //
243            // |    |RAAA| -- retry after refblock; need a new refblock again
244            // |R   |RAAA|AAAA| -- the new refblock allocates itself in the region we abandoned
245            //
246            // However, eventually, the new refblocks will run into the new start of our allocation
247            // again:
248            //
249            // |RRRR|RAAA|AAAA|AAAA|AAAA|AAAA| -- need new refblock
250            // |RRRR|RAAA|AAAA|AAAA|AAAA|AAAA|R   | -- allocation cannot go on, again
251            // |RRRR|R   |    |    |    |    |RAAA| -- another attempt
252            // |RRRR|RRRR|R...|    |    |    |RAAA|AAAA|AAAA|AAAA|AAAA|...
253            //
254            // As you can see, the hole we leave behind gets larger each time.  So eventually, this
255            // must converge.
256            //
257            // The same applies to the refcount table being allocated instead of just refblocks.
258
259            let result = self.allocate_cluster_at(index).await;
260            if !matches!(result, Ok(true)) {
261                // Already allocated, or some real error occurred; free everything allocated so far
262                self.free_clusters(start_index, index - start_index).await;
263                return result.map(|_| index - start_index);
264            }
265
266            count -= ClusterCount(1);
267            index += ClusterCount(1);
268        }
269
270        Ok(index - start_index)
271    }
272
273    /// Allocate the given cluster in the image file.
274    ///
275    /// Return `Ok(true)` if allocation was successful, or `Ok(false)` if the cluster was already
276    /// allocated before.
277    async fn allocate_cluster_at(&mut self, index: HostCluster) -> io::Result<bool> {
278        let rb_bits = self.header.rb_bits();
279        let (rt_index, rb_index) = index.rt_rb_indices(rb_bits);
280
281        let rb = self.ensure_rb(rt_index).await?;
282        let mut rb = rb.lock_write().await;
283        let can_allocate = rb.is_zero(rb_index);
284        if can_allocate {
285            rb.increment(rb_index)?;
286        }
287
288        // We now know this is allocated
289        if index == self.first_free_cluster {
290            self.first_free_cluster = index + ClusterCount(1);
291        }
292
293        Ok(can_allocate)
294    }
295
296    /// Get the refblock referenced by the given reftable index, if any.
297    ///
298    /// If there is no refblock for the given reftable index, return `Ok(None)`.
299    async fn get_rb(&mut self, rt_index: usize) -> io::Result<Option<Arc<RefBlock>>> {
300        let rt_entry = self.reftable.get(rt_index);
301        if let Some(rb_offset) = rt_entry.refblock_offset() {
302            let cb = self.header.cluster_bits();
303            let rb_cluster = rb_offset.checked_cluster(cb).ok_or_else(|| {
304                invalid_data(format!("Unaligned refcount block with index {rt_index}; refcount table entry: {rt_entry:?}"))
305            })?;
306
307            self.caches.rb_get_or_insert(rb_cluster).await.map(Some)
308        } else {
309            Ok(None)
310        }
311    }
312
313    /// Get a refblock for the given reftable index.
314    ///
315    /// If there already is a refblock at that index, return it.  Otherwise, create one and hook it
316    /// up.
317    async fn ensure_rb(&mut self, rt_index: usize) -> io::Result<Arc<RefBlock>> {
318        if let Some(rb) = self.get_rb(rt_index).await? {
319            return Ok(rb);
320        }
321
322        if !self.reftable.in_bounds(rt_index) {
323            self.grow_reftable(rt_index).await?;
324            // `grow_reftable` will allocate new refblocks, so check the index again
325            if let Some(rb) = self.get_rb(rt_index).await? {
326                return Ok(rb);
327            }
328        }
329
330        let mut new_rb = RefBlock::new_cleared(self.file.as_ref(), &self.header)?;
331
332        // This is the first cluster covered by the new refblock
333        let rb_cluster = HostCluster::from_ref_indices(rt_index, 0, self.header.rb_bits());
334
335        // Try to allocate a cluster in the already existing refcount structures.
336        // By stopping looking for clusters at `rb_cluster`, we ensure that we will not land here
337        // in this exact function again, trying to allocate the very same refblock (it is possible
338        // we allocate one before the current one, though), and so prevent any possible infinite
339        // recursion.
340        let alloc_fut_or_result = self.allocate_clusters(ClusterCount(1), Some(rb_cluster));
341
342        // Recursion is possible, though, so in async mode, any future must be boxed
343        #[cfg(feature = "async")]
344        let alloc_result = Box::pin(alloc_fut_or_result).await;
345        // Whereas in sync mode, we now already have the result
346        #[cfg(feature = "sync")]
347        let alloc_result = alloc_fut_or_result;
348
349        if let Ok(new_rb_cluster) = alloc_result {
350            new_rb.set_cluster(new_rb_cluster);
351        } else {
352            // Place the refblock such that it covers itself
353            new_rb.set_cluster(rb_cluster);
354            new_rb.lock_write().await.increment(0)?;
355        }
356        new_rb.write(self.file.as_ref()).await?;
357
358        self.reftable.enter_refblock(rt_index, &new_rb)?;
359        self.reftable
360            .write_entry(self.file.as_ref(), rt_index)
361            .await?;
362
363        let new_rb = Arc::new(new_rb);
364        self.caches
365            .rb_insert(new_rb.get_cluster().unwrap(), Arc::clone(&new_rb))
366            .await?;
367        Ok(new_rb)
368    }
369
370    /// Create a new refcount table covering at least `at_least_index`.
371    ///
372    /// Create a new reftable of the required size, copy all existing refblock references into it,
373    /// ensure it is refcounted itself (also creating new refblocks if necessary), and have the
374    /// image header reference the new refcount table.
375    async fn grow_reftable(&mut self, at_least_index: usize) -> io::Result<()> {
376        let cb = self.header.cluster_bits();
377        let rb_bits = self.header.rb_bits();
378        let rb_entries = 1 << rb_bits;
379
380        let mut new_rt = self.reftable.clone_and_grow(&self.header, at_least_index)?;
381        let rt_clusters = ClusterCount::from_byte_size(new_rt.byte_size() as u64, cb);
382
383        // Find free range
384        let (mut rt_index, mut rb_index) = self.first_free_cluster.rt_rb_indices(rb_bits);
385        let mut free_cluster_index: Option<HostCluster> = None;
386        let mut free_cluster_count = ClusterCount(0);
387
388        // Number of clusters required to allocate both the new reftable and all new refblocks.
389        // Note that `clone_and_grow()` *guarantees* we can fit the final count in there.
390        let mut required_clusters = rt_clusters;
391
392        while free_cluster_count < required_clusters {
393            // `clone_and_grow()` guarantees it can fit
394            assert!(new_rt.in_bounds(rt_index));
395
396            let rt_entry = new_rt.get(rt_index);
397            let Some(rb_offset) = rt_entry.refblock_offset() else {
398                let start_index = HostCluster::from_ref_indices(rt_index, 0, rb_bits);
399                free_cluster_index.get_or_insert(start_index);
400                free_cluster_count += ClusterCount(rb_entries as u64);
401                // Need to allocate this RB
402                required_clusters += ClusterCount(1);
403                continue;
404            };
405
406            let rb_cluster = rb_offset.checked_cluster(cb).ok_or_else(|| {
407                invalid_data(format!("Unaligned refcount block with index {rt_index}; refcount table entry: {rt_entry:?}"))
408            })?;
409
410            let rb = self.caches.rb_get_or_insert(rb_cluster).await?;
411            for i in rb_index..rb_entries {
412                if rb.is_zero(i) {
413                    let index = HostCluster::from_ref_indices(rt_index, i, rb_bits);
414                    free_cluster_index.get_or_insert(index);
415                    free_cluster_count += ClusterCount(1);
416
417                    if free_cluster_count >= required_clusters {
418                        break;
419                    }
420                } else if free_cluster_index.is_some() {
421                    free_cluster_index.take();
422                    free_cluster_count = ClusterCount(0);
423                    required_clusters = rt_clusters; // reset
424                }
425            }
426
427            rb_index = 0;
428            rt_index += 1;
429        }
430
431        let mut index = free_cluster_index.unwrap();
432        let mut count = required_clusters;
433
434        // Put refblocks first
435        let rt_index_start = index.rt_index(rb_bits);
436        let rt_index_end = (index + count).0.div_ceil(rb_entries as u64) as usize;
437
438        let mut refblocks = Vec::<Arc<RefBlock>>::new();
439        for rt_i in rt_index_start..rt_index_end {
440            if let Some(rb_offset) = new_rt.get(rt_i).refblock_offset() {
441                // Checked in the loop above
442                let rb_cluster = rb_offset.checked_cluster(cb).unwrap();
443                let rb = self.caches.rb_get_or_insert(rb_cluster).await?;
444                refblocks.push(rb);
445                continue;
446            }
447
448            let mut rb = RefBlock::new_cleared(self.file.as_ref(), &self.header)?;
449            rb.set_cluster(index);
450            new_rt.enter_refblock(rt_i, &rb)?;
451            let rb = Arc::new(rb);
452            self.caches.rb_insert(index, Arc::clone(&rb)).await?;
453            refblocks.push(rb);
454            index += ClusterCount(1);
455            count -= ClusterCount(1);
456        }
457
458        assert!(count >= rt_clusters);
459        new_rt.set_cluster(index);
460
461        // Now set allocation information
462        let start_index = free_cluster_index.unwrap();
463        let end_index = index + rt_clusters;
464
465        for index in start_index.0..end_index.0 {
466            let index = HostCluster(index);
467            let (rt_i, rb_i) = index.rt_rb_indices(rb_bits);
468
469            // `refblocks[0]` is for `rt_index_start`
470            let rb_vec_i = rt_i - rt_index_start;
471            // Incrementing from 0 to 1 must succeed
472            refblocks[rb_vec_i]
473                .lock_write()
474                .await
475                .increment(rb_i)
476                .unwrap();
477        }
478
479        // Any errors from here on may lead to leaked clusters if there are refblocks in
480        // `refblocks` that are already part of the old reftable.
481        // TODO: Try to clean that up, though it seems quite hard for little gain.
482        self.caches.flush_rb().await?;
483        new_rt.write(self.file.as_ref()).await?;
484
485        self.header.set_reftable(&new_rt)?;
486        self.header
487            .write_reftable_pointer(self.file.as_ref())
488            .await?;
489
490        // Must set new reftable before calling `free_clusters()`
491        let mut old_reftable = mem::replace(&mut self.reftable, new_rt);
492        if let Some(old_rt_cluster) = old_reftable.get_cluster() {
493            let old_rt_size = old_reftable.cluster_count();
494            old_reftable.unset_cluster();
495            self.free_clusters(old_rt_cluster, old_rt_size).await;
496        }
497
498        Ok(())
499    }
500
501    /// Free clusters (i.e. decrement their refcount).
502    ///
503    /// Best-effort operation.  On error, the given clusters may be leaked, but no errors are ever
504    /// returned (because there is no good way to handle such errors anyway).
505    async fn free_clusters(&mut self, start: HostCluster, mut count: ClusterCount) {
506        if count.0 == 0 {
507            return;
508        }
509
510        if start < self.first_free_cluster {
511            self.first_free_cluster = start;
512        }
513
514        let rb_bits = self.header.rb_bits();
515        let rb_entries = 1 << rb_bits;
516        let (mut rt_index, mut rb_index) = start.rt_rb_indices(rb_bits);
517
518        while count > ClusterCount(0) {
519            let in_rb_count = cmp::min((rb_entries - rb_index) as u64, count.0) as usize;
520
521            match self.get_rb(rt_index).await {
522                Ok(Some(rb)) => {
523                    let mut rb = rb.lock_write().await;
524                    for i in rb_index..(rb_index + in_rb_count) {
525                        if let Err(err) = rb.decrement(i) {
526                            event!(Level::WARN, "Failed to free cluster: {err}");
527                        }
528                    }
529                }
530
531                Ok(None) => {
532                    event!(
533                        Level::WARN,
534                        "Failed to free {in_rb_count} clusters: Not allocated"
535                    )
536                }
537                Err(err) => event!(Level::WARN, "Failed to free {in_rb_count} clusters: {err}"),
538            }
539
540            count -= ClusterCount(in_rb_count as u64);
541            rb_index = 0;
542            rt_index += 1;
543        }
544    }
545}