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 parking_lot::Mutex as SyncMutex;
9use std::mem;
10use tracing::{event, warn, Level};
11
12/// List of clusters that are no longer in use, and should be freed.
13///
14/// Shared between [`Qcow2`] and its [`Allocator`].
15#[derive(Default)]
16pub(super) struct ClustersToFreeList {
17 /// The list itself.
18 ///
19 /// Synchronous mutex because it is never to be held across `.await` points.
20 list: SyncMutex<Vec<ClustersToFreeEntry>>,
21}
22
23impl ClustersToFreeList {
24 /// Mark `entry` as a cluster to be freed.
25 pub fn push(&self, entry: ClustersToFreeEntry) {
26 self.list.lock().push(entry);
27 }
28
29 /// Check whether the list is empty.
30 pub fn is_empty(&self) -> bool {
31 self.list.lock().is_empty()
32 }
33
34 /// Remove all entries from the list and return them.
35 pub fn take(&self) -> Vec<ClustersToFreeEntry> {
36 mem::take(&mut *self.list.lock())
37 }
38
39 /// Create a guard that enters `entry` into the list when dropped, unless disarmed.
40 ///
41 /// This is for use in allocation paths, where a cluster is allocated, not yet hooked up, and
42 /// anything might cause the hooking-up not to happen, in which case `entry` should be dropped.
43 /// Once it is hooked up, the guard will be disarmed, thus making the cluster permanent.
44 pub fn pending_guard(self: &Arc<Self>, entry: ClustersToFreeEntry) -> PendingClusters {
45 PendingClusters {
46 clusters_to_free: Arc::clone(self),
47 entry: Some(entry),
48 }
49 }
50}
51
52/// Guard for newly allocated clusters that are not yet referenced by any metadata.
53///
54/// If the operation that allocated them fails or is cancelled (i.e. its future is dropped) before
55/// it can reference them, dropping this guard enters them into [`ClustersToFreeList`], so they are
56/// not leaked. Once they are referenced (at least in memory), disarm the guard.
57#[must_use = "Dropping the guard frees the clusters"]
58pub(super) struct PendingClusters {
59 /// List to enter the clusters into.
60 clusters_to_free: Arc<ClustersToFreeList>,
61
62 /// The clusters; `None` when disarmed.
63 entry: Option<ClustersToFreeEntry>,
64}
65
66impl PendingClusters {
67 /// First cluster of the allocation.
68 pub fn cluster(&self) -> HostCluster {
69 // Only `None` after `make_persistent()`, which consumes `self`
70 self.entry.as_ref().unwrap().first_cluster
71 }
72
73 /// Disarm the guard, moving the clusters from a pending state (to be freed when something goes
74 /// wrong) to a permanent state, because they are referenced now.
75 pub fn make_persistent(mut self) {
76 self.entry.take();
77 }
78}
79
80impl Drop for PendingClusters {
81 fn drop(&mut self) {
82 if let Some(entry) = self.entry.take() {
83 self.clusters_to_free.push(entry);
84 }
85 }
86}
87
88/// Entry in [`ClustersToFreeList`]: A range of clusters to be freed.
89pub(super) struct ClustersToFreeEntry {
90 /// First cluster in the range.
91 first_cluster: HostCluster,
92
93 /// Number of clusters in the range.
94 count: ClusterCount,
95
96 /// Whether these are metadata clusters (or data clusters).
97 metadata: bool,
98}
99
100impl ClustersToFreeEntry {
101 /// Create an entry for the given range of data clusters.
102 pub fn data(first_cluster: HostCluster, count: ClusterCount) -> Self {
103 ClustersToFreeEntry {
104 first_cluster,
105 count,
106 metadata: false,
107 }
108 }
109
110 /// Create an entry for the given range of metadata clusters.
111 pub fn metadata(first_cluster: HostCluster, count: ClusterCount) -> Self {
112 ClustersToFreeEntry {
113 first_cluster,
114 count,
115 metadata: true,
116 }
117 }
118}
119
120/// Central facility for cluster allocation.
121pub(super) struct Allocator<S: Storage> {
122 /// Qcow2 metadata file.
123 file: Arc<S>,
124
125 /// Qcow2 refcount table.
126 reftable: RefTable,
127
128 /// The first free cluster index in the qcow2 file, to speed up allocation.
129 first_free_cluster: HostCluster,
130
131 /// Qcow2 image header.
132 header: Arc<Header>,
133
134 /// L2 and refblock caches with dependency management.
135 caches: Arc<MetadataCaches<S>>,
136
137 /// Clusters that are no longer referenced by image metadata, but not yet freed.
138 ///
139 /// Shared with [`Qcow2`].
140 clusters_to_free: Arc<ClustersToFreeList>,
141}
142
143#[maybe_async]
144impl<S: Storage + 'static, F: WrappedFormat<S> + 'static> Qcow2<S, F> {
145 /// Return the central allocator instance.
146 ///
147 /// Returns an error for read-only images.
148 async fn allocator(&self) -> io::Result<MutexGuard<'_, Allocator<S>>> {
149 Ok(self
150 .allocator
151 .as_ref()
152 .ok_or_else(|| io::Error::other("Image is read-only"))?
153 .lock()
154 .await)
155 }
156
157 /// Allocate one metadata cluster.
158 ///
159 /// Metadata clusters are allocated exclusively in the metadata (image) file.
160 ///
161 /// The returned guard frees the cluster when dropped, unless it is disarmed by referencing the
162 /// cluster.
163 pub(super) async fn allocate_meta_cluster(&self) -> io::Result<PendingClusters> {
164 self.allocate_meta_clusters(ClusterCount(1)).await
165 }
166
167 /// Allocate multiple continuous metadata clusters.
168 ///
169 /// Useful e.g. for the L1 table or refcount table.
170 ///
171 /// The returned guard frees the clusters when dropped, unless it is disarmed by referencing
172 /// the clusters.
173 pub(super) async fn allocate_meta_clusters(
174 &self,
175 count: ClusterCount,
176 ) -> io::Result<PendingClusters> {
177 self.allocator()
178 .await?
179 .allocate_clusters(count, None, true)
180 .await
181 }
182
183 /// Allocate one data clusters for the given guest cluster.
184 ///
185 /// Without an external data file, data clusters are allocated in the image file, just like
186 /// metadata clusters.
187 ///
188 /// With an external data file, data clusters aren’t really allocated, but just put there at
189 /// the same offset as their guest offset. Their refcount is not tracked by the qcow2 metadata
190 /// structures (which only cover the metadata (image) file).
191 ///
192 /// The returned guard frees the cluster when dropped, unless it is disarmed by referencing the
193 /// cluster. (With an external data file, freeing is a no-op.)
194 pub(super) async fn allocate_data_cluster(
195 &self,
196 guest_cluster: GuestCluster,
197 ) -> io::Result<PendingClusters> {
198 if self.header.external_data_file() {
199 let cluster = HostCluster(guest_cluster.0);
200 Ok(self
201 .clusters_to_free
202 .pending_guard(ClustersToFreeEntry::data(cluster, ClusterCount(1))))
203 } else {
204 let mut allocator = self.allocator().await?;
205
206 // Allocate clusters before setting up L2 entries
207 self.caches.l2_depends_on_rb().await?;
208
209 allocator
210 .allocate_clusters(ClusterCount(1), None, false)
211 .await
212 }
213 }
214
215 /// Allocate the data cluster with the given index.
216 ///
217 /// Without a `mandatory_host_cluster` given, this is the same as
218 /// [`Qcow2::allocate_data_cluster()`].
219 ///
220 /// With a `mandatory_host_cluster` given, try to allocate that cluster. If that is not
221 /// possible because it is already allocated, return `Ok(None)`.
222 pub(super) async fn allocate_data_cluster_at(
223 &self,
224 guest_cluster: GuestCluster,
225 mandatory_host_cluster: Option<HostCluster>,
226 ) -> io::Result<Option<PendingClusters>> {
227 let Some(mandatory_host_cluster) = mandatory_host_cluster else {
228 return self.allocate_data_cluster(guest_cluster).await.map(Some);
229 };
230
231 let allocated = if self.header.external_data_file() {
232 HostCluster(guest_cluster.0) == mandatory_host_cluster
233 } else {
234 let mut allocator = self.allocator().await?;
235
236 // Allocate clusters before setting up L2 entries
237 self.caches.l2_depends_on_rb().await?;
238
239 allocator
240 .allocate_cluster_at(mandatory_host_cluster)
241 .await?
242 };
243
244 Ok(allocated.then(|| {
245 self.clusters_to_free
246 .pending_guard(ClustersToFreeEntry::data(
247 mandatory_host_cluster,
248 ClusterCount(1),
249 ))
250 }))
251 }
252
253 /// Free all clusters in `self.clusters_to_free`.
254 ///
255 /// Best-effort operation, like [`Allocator::process_clusters_to_free()`].
256 pub(super) async fn process_clusters_to_free(&self) {
257 // Skip taking the allocator lock if there is nothing to do
258 if self.clusters_to_free.is_empty() {
259 return;
260 }
261
262 if let Ok(mut allocator) = self.allocator().await {
263 allocator.process_clusters_to_free().await;
264 }
265 }
266}
267
268#[maybe_async]
269impl<S: Storage> Allocator<S> {
270 /// Create a new allocator for the given image file.
271 pub async fn new(
272 image: Arc<S>,
273 header: Arc<Header>,
274 caches: Arc<MetadataCaches<S>>,
275 clusters_to_free: Arc<ClustersToFreeList>,
276 ) -> io::Result<Self> {
277 let cb = header.cluster_bits();
278 let rt_offset = header.reftable_offset();
279 let rt_cluster = rt_offset
280 .checked_cluster(cb)
281 .ok_or_else(|| invalid_data(format!("Unaligned refcount table: {rt_offset}")))?;
282
283 let reftable = RefTable::load(
284 image.as_ref(),
285 &header,
286 rt_cluster,
287 header.reftable_entries(),
288 )
289 .await?;
290
291 Ok(Allocator {
292 file: image,
293 reftable,
294 first_free_cluster: HostCluster(0),
295 header,
296 caches,
297 clusters_to_free,
298 })
299 }
300
301 /// Free all clusters in `self.clusters_to_free` (i.e. decrement their refcount).
302 ///
303 /// Must not be called while allocating clusters: Freeing data clusters requires the refblock
304 /// cache to depend on the L2 cache, which is the opposite of what allocating requires.
305 ///
306 /// Best-effort operation. On error, the given clusters may be leaked, but no errors are ever
307 /// returned (because there is no good way to handle such errors anyway).
308 async fn process_clusters_to_free(&mut self) {
309 let (metadata_clusters, mut data_clusters): (Vec<_>, Vec<_>) = self
310 .clusters_to_free
311 .take()
312 .into_iter()
313 .partition(|entry| entry.metadata);
314
315 // The refcount structures only track allocations in the metadata file (the qcow2 file), so
316 // when freeing data clusters with an external data file, nothing is to be done
317 if self.header.external_data_file() {
318 data_clusters.clear();
319 }
320
321 if metadata_clusters.is_empty() && data_clusters.is_empty() {
322 return;
323 }
324
325 // Writes whose futures have been dropped may still be in flight to these clusters, and
326 // must not be performed after the clusters have been reused.
327 // For data clusters, that can be any cancelled guest write; for metadata clusters, it
328 // could for example be a canceled L1 or reftable entry write, after which the table is
329 // grown and the old one freed.
330 // In any case, all of these clusters will be in the qcow2 (metadata) file, because data
331 // clusters in external data files do not need this treatment.
332 if let Err(err) = self.file.settle_writes().await {
333 warn!("Leaking clusters; cannot settle writes: {err}");
334 return;
335 }
336
337 for entry in metadata_clusters {
338 self.free_clusters(entry.first_cluster, entry.count).await;
339 }
340
341 if data_clusters.is_empty() {
342 return;
343 }
344
345 // Clear L2 entries before deallocating data clusters
346 if let Err(err) = self.caches.rb_depends_on_l2().await {
347 warn!("Leaking clusters; cannot set up cache dependency: {err}");
348 return;
349 }
350
351 for entry in data_clusters {
352 self.free_clusters(entry.first_cluster, entry.count).await;
353 }
354 }
355
356 /// Invaidate the refcount block cache.
357 ///
358 /// # Safety
359 /// May cause image corruption, you must guarantee the on-disk state is consistent.
360 pub async unsafe fn invalidate_rb_cache(&self) -> io::Result<()> {
361 // Safe: Caller says so.
362 unsafe { self.caches.invalidate_rb() }.await
363 }
364
365 /// Allocate clusters in the image file.
366 ///
367 /// `end_cluster` should only be used when allocating refblocks. When reaching this cluster
368 /// index, abort trying to allocate. (This is used for allocating refblocks, to prevent
369 /// infinite recursion and speed things up.)
370 ///
371 /// `metadata` specifies whether the clusters are to be used for metadata (true) or data
372 /// (false), in order to create the right kind of [`ClustersToFreeEntry`] for the returned
373 /// [`PendingClusters`] guard. That returned guard frees the cluster when dropped, unless it
374 /// is disarmed by referencing the cluster.
375 async fn allocate_clusters(
376 &mut self,
377 count: ClusterCount,
378 end_cluster: Option<HostCluster>,
379 metadata: bool,
380 ) -> io::Result<PendingClusters> {
381 let mut index = self.first_free_cluster;
382 loop {
383 if end_cluster == Some(index) {
384 return Err(io::Error::other("Maximum cluster index reached"));
385 }
386
387 let alloc_count = self.allocate_clusters_at(index, count).await?;
388 if alloc_count == count {
389 let to_free_entry = if metadata {
390 ClustersToFreeEntry::metadata(index, count)
391 } else {
392 ClustersToFreeEntry::data(index, count)
393 };
394 return Ok(self.clusters_to_free.pending_guard(to_free_entry));
395 }
396
397 index += alloc_count + ClusterCount(1);
398 if index.offset(self.header.cluster_bits()) > MAX_OFFSET {
399 return Err(io::Error::other("Cannot grow qcow2 file any further"));
400 }
401 }
402 }
403
404 /// Allocate the given clusters in the image file.
405 ///
406 /// Allocate up to `count` unallocated clusters starting from `index`. When encountering an
407 /// already allocated cluster (or any other error), stop, and free the clusters that were just
408 /// newly allocated.
409 ///
410 /// Returns the number of clusters that could be allocated (starting from `index`), which may
411 /// be 0 if `index` has already been allocated. Note again that in case this is less than
412 /// `count`, those clusters will have been freed again already, so this is just a hint to
413 /// callers that the cluster at `index + count` is already allocated.
414 async fn allocate_clusters_at(
415 &mut self,
416 mut index: HostCluster,
417 mut count: ClusterCount,
418 ) -> io::Result<ClusterCount> {
419 let start_index = index;
420
421 while count > ClusterCount(0) {
422 // Note that `ensure_rb()` in `allocate_cluster_at()` may allocate clusters (new
423 // refblocks), and also a new refcount table. This can interfere with us allocating a
424 // large continuous region like so (A is our allocation, R is a refblock, imagine a
425 // refblock covers four clusters):
426 //
427 // |AAAA| -- allocated four clusters need new refblock
428 // |AAAA|R | -- made refblock self-describing, but now allocation cannot go on
429 //
430 // This gets resolved by us retrying, and future refblocks using the region that has
431 // now become free but already has refblocks to cover it:
432 //
433 // | |RAAA| -- retry after refblock; need a new refblock again
434 // |R |RAAA|AAAA| -- the new refblock allocates itself in the region we abandoned
435 //
436 // However, eventually, the new refblocks will run into the new start of our allocation
437 // again:
438 //
439 // |RRRR|RAAA|AAAA|AAAA|AAAA|AAAA| -- need new refblock
440 // |RRRR|RAAA|AAAA|AAAA|AAAA|AAAA|R | -- allocation cannot go on, again
441 // |RRRR|R | | | | |RAAA| -- another attempt
442 // |RRRR|RRRR|R...| | | |RAAA|AAAA|AAAA|AAAA|AAAA|...
443 //
444 // As you can see, the hole we leave behind gets larger each time. So eventually, this
445 // must converge.
446 //
447 // The same applies to the refcount table being allocated instead of just refblocks.
448
449 let result = self.allocate_cluster_at(index).await;
450 if !matches!(result, Ok(true)) {
451 // Already allocated, or some real error occurred; free everything allocated so far.
452 // Do not go through `self.clusters_to_free`: As described above, the freed range
453 // must be available for allocation immediately, so that retrying converges.
454 self.free_clusters(start_index, index - start_index).await;
455 return result.map(|_| index - start_index);
456 }
457
458 count -= ClusterCount(1);
459 index += ClusterCount(1);
460 }
461
462 Ok(index - start_index)
463 }
464
465 /// Allocate the given cluster in the image file.
466 ///
467 /// Return `Ok(true)` if allocation was successful, or `Ok(false)` if the cluster was already
468 /// allocated before.
469 async fn allocate_cluster_at(&mut self, index: HostCluster) -> io::Result<bool> {
470 let rb_bits = self.header.rb_bits();
471 let (rt_index, rb_index) = index.rt_rb_indices(rb_bits);
472
473 let rb = self.ensure_rb(rt_index).await?;
474 let mut rb = rb.lock_write().await;
475 let can_allocate = rb.is_zero(rb_index);
476 if can_allocate {
477 rb.increment(rb_index)?;
478 }
479
480 // We now know this is allocated
481 if index == self.first_free_cluster {
482 self.first_free_cluster = index + ClusterCount(1);
483 }
484
485 Ok(can_allocate)
486 }
487
488 /// Get the refblock referenced by the given reftable index, if any.
489 ///
490 /// If there is no refblock for the given reftable index, return `Ok(None)`.
491 async fn get_rb(&mut self, rt_index: usize) -> io::Result<Option<Arc<RefBlock>>> {
492 let rt_entry = self.reftable.get(rt_index);
493 if let Some(rb_offset) = rt_entry.refblock_offset() {
494 let cb = self.header.cluster_bits();
495 let rb_cluster = rb_offset.checked_cluster(cb).ok_or_else(|| {
496 invalid_data(format!("Unaligned refcount block with index {rt_index}; refcount table entry: {rt_entry:?}"))
497 })?;
498
499 self.caches.rb_get_or_insert(rb_cluster).await.map(Some)
500 } else {
501 Ok(None)
502 }
503 }
504
505 /// Get a refblock for the given reftable index.
506 ///
507 /// If there already is a refblock at that index, return it. Otherwise, create one and hook it
508 /// up.
509 async fn ensure_rb(&mut self, rt_index: usize) -> io::Result<Arc<RefBlock>> {
510 if let Some(rb) = self.get_rb(rt_index).await? {
511 return Ok(rb);
512 }
513
514 if !self.reftable.in_bounds(rt_index) {
515 self.grow_reftable(rt_index).await?;
516 // `grow_reftable` will allocate new refblocks, so check the index again
517 if let Some(rb) = self.get_rb(rt_index).await? {
518 return Ok(rb);
519 }
520 }
521
522 let mut new_rb = RefBlock::new_cleared(self.file.as_ref(), &self.header)?;
523
524 // This is the first cluster covered by the new refblock
525 let rb_cluster = HostCluster::from_ref_indices(rt_index, 0, self.header.rb_bits());
526
527 // Try to allocate a cluster in the already existing refcount structures.
528 // By stopping looking for clusters at `rb_cluster`, we ensure that we will not land here
529 // in this exact function again, trying to allocate the very same refblock (it is possible
530 // we allocate one before the current one, though), and so prevent any possible infinite
531 // recursion.
532 let alloc_fut_or_result = self.allocate_clusters(ClusterCount(1), Some(rb_cluster), true);
533
534 // Recursion is possible, though, so in async mode, any future must be boxed
535 #[cfg(feature = "async")]
536 let alloc_result = Box::pin(alloc_fut_or_result).await;
537 // Whereas in sync mode, we now already have the result
538 #[cfg(feature = "sync")]
539 let alloc_result = alloc_fut_or_result;
540
541 let new_rb_allocation = if let Ok(new_rb_allocation) = alloc_result {
542 new_rb.set_cluster(new_rb_allocation.cluster());
543 Some(new_rb_allocation)
544 } else {
545 // Place the refblock such that it covers itself. Its refcount is stored only in
546 // itself, so until it is referenced, there is nothing that could be leaked.
547 new_rb.set_cluster(rb_cluster);
548 new_rb.lock_write().await.increment(0)?;
549 None
550 };
551 new_rb.write(self.file.as_ref()).await?;
552
553 self.reftable
554 .enter_refblock(rt_index, &new_rb, new_rb_allocation)?;
555 self.reftable
556 .write_entry(self.file.as_ref(), rt_index)
557 .await?;
558
559 let new_rb = Arc::new(new_rb);
560 self.caches.rb_insert(Arc::clone(&new_rb)).await?;
561 Ok(new_rb)
562 }
563
564 /// Create a new refcount table covering at least `at_least_index`.
565 ///
566 /// Create a new reftable of the required size, copy all existing refblock references into it,
567 /// ensure it is refcounted itself (also creating new refblocks if necessary), and have the
568 /// image header reference the new refcount table.
569 async fn grow_reftable(&mut self, at_least_index: usize) -> io::Result<()> {
570 let cb = self.header.cluster_bits();
571 let rb_bits = self.header.rb_bits();
572 let rb_entries = 1 << rb_bits;
573
574 let mut new_rt = self.reftable.clone_and_grow(&self.header, at_least_index)?;
575 let rt_clusters = ClusterCount::from_byte_size(new_rt.byte_size() as u64, cb);
576
577 // Find free range
578 let (mut rt_index, mut rb_index) = self.first_free_cluster.rt_rb_indices(rb_bits);
579 let mut free_cluster_index: Option<HostCluster> = None;
580 let mut free_cluster_count = ClusterCount(0);
581
582 // Number of clusters required to allocate both the new reftable and all new refblocks.
583 // Note that `clone_and_grow()` *guarantees* we can fit the final count in there.
584 let mut required_clusters = rt_clusters;
585
586 while free_cluster_count < required_clusters {
587 // `clone_and_grow()` guarantees it can fit
588 assert!(new_rt.in_bounds(rt_index));
589
590 let rt_entry = new_rt.get(rt_index);
591 if let Some(rb_offset) = rt_entry.refblock_offset() {
592 let rb_cluster = rb_offset.checked_cluster(cb).ok_or_else(|| {
593 invalid_data(format!(
594 "Unaligned refcount block with index {rt_index}; refcount table entry: {rt_entry:?}"
595 ))
596 })?;
597
598 let rb = self.caches.rb_get_or_insert(rb_cluster).await?;
599 for i in rb_index..rb_entries {
600 if rb.is_zero(i) {
601 let index = HostCluster::from_ref_indices(rt_index, i, rb_bits);
602 free_cluster_index.get_or_insert(index);
603 free_cluster_count += ClusterCount(1);
604
605 if free_cluster_count >= required_clusters {
606 break;
607 }
608 } else if free_cluster_index.is_some() {
609 free_cluster_index.take();
610 free_cluster_count = ClusterCount(0);
611 required_clusters = rt_clusters; // reset
612 }
613 }
614 } else {
615 let start_index = HostCluster::from_ref_indices(rt_index, rb_index, rb_bits);
616 free_cluster_index.get_or_insert(start_index);
617 free_cluster_count += ClusterCount((rb_entries - rb_index) as u64);
618 // Need to allocate this RB
619 required_clusters += ClusterCount(1);
620 }
621
622 rb_index = 0;
623 rt_index += 1;
624 }
625
626 let mut index = free_cluster_index.unwrap();
627 let mut count = required_clusters;
628
629 // Put refblocks first
630 let rt_index_start = index.rt_index(rb_bits);
631 let rt_index_end = (index + count).0.div_ceil(rb_entries as u64) as usize;
632
633 let mut refblocks = Vec::<Arc<RefBlock>>::new();
634 for rt_i in rt_index_start..rt_index_end {
635 if let Some(rb_offset) = new_rt.get(rt_i).refblock_offset() {
636 // Checked in the loop above
637 let rb_cluster = rb_offset.checked_cluster(cb).unwrap();
638 let rb = self.caches.rb_get_or_insert(rb_cluster).await?;
639 refblocks.push(rb);
640 continue;
641 }
642
643 let mut rb = RefBlock::new_cleared(self.file.as_ref(), &self.header)?;
644 rb.set_cluster(index);
645 new_rt.enter_refblock(rt_i, &rb, None)?;
646 let rb = Arc::new(rb);
647 self.caches.rb_insert(Arc::clone(&rb)).await?;
648 refblocks.push(rb);
649 index += ClusterCount(1);
650 count -= ClusterCount(1);
651 }
652
653 assert!(count >= rt_clusters);
654 new_rt.set_cluster(index);
655
656 // Now set allocation information
657 let start_index = free_cluster_index.unwrap();
658 let end_index = index + rt_clusters;
659
660 for index in start_index.0..end_index.0 {
661 let index = HostCluster(index);
662 let (rt_i, rb_i) = index.rt_rb_indices(rb_bits);
663
664 // `refblocks[0]` is for `rt_index_start`
665 let rb_vec_i = rt_i - rt_index_start;
666 // Incrementing from 0 to 1 must succeed
667 refblocks[rb_vec_i]
668 .lock_write()
669 .await
670 .increment(rb_i)
671 .unwrap();
672 }
673
674 // Any errors from here on may lead to leaked clusters if there are refblocks in
675 // `refblocks` that are already part of the old reftable.
676 // TODO: Try to clean that up, though it seems quite hard for little gain.
677 self.caches.flush_rb().await?;
678 new_rt.write(self.file.as_ref()).await?;
679
680 self.header.set_reftable(&new_rt)?;
681 self.header
682 .write_reftable_pointer(self.file.as_ref())
683 .await?;
684
685 // Must set new reftable before freeing the old reftable’s clusters
686 let mut old_reftable = mem::replace(&mut self.reftable, new_rt);
687 if let Some(old_rt_cluster) = old_reftable.get_cluster() {
688 let old_rt_size = old_reftable.cluster_count();
689 old_reftable.unset_cluster();
690 // Do not run `process_clusters_to_free()` here: We are in the middle of allocating
691 // clusters, and that function would flip the L2-RB cache dependency. Our caller will
692 // drain the list eventually.
693 self.clusters_to_free
694 .push(ClustersToFreeEntry::metadata(old_rt_cluster, old_rt_size));
695 }
696
697 Ok(())
698 }
699
700 /// Free clusters (i.e. decrement their refcount).
701 ///
702 /// Best-effort operation. On error, the given clusters may be leaked, but no errors are ever
703 /// returned (because there is no good way to handle such errors anyway).
704 async fn free_clusters(&mut self, start: HostCluster, mut count: ClusterCount) {
705 if count.0 == 0 {
706 return;
707 }
708
709 if start < self.first_free_cluster {
710 self.first_free_cluster = start;
711 }
712
713 let rb_bits = self.header.rb_bits();
714 let rb_entries = 1 << rb_bits;
715 let (mut rt_index, mut rb_index) = start.rt_rb_indices(rb_bits);
716
717 while count > ClusterCount(0) {
718 let in_rb_count = cmp::min((rb_entries - rb_index) as u64, count.0) as usize;
719
720 match self.get_rb(rt_index).await {
721 Ok(Some(rb)) => {
722 let mut rb = rb.lock_write().await;
723 for i in rb_index..(rb_index + in_rb_count) {
724 if let Err(err) = rb.decrement(i) {
725 event!(Level::WARN, "Failed to free cluster: {err}");
726 }
727 }
728 }
729
730 Ok(None) => {
731 event!(
732 Level::WARN,
733 "Failed to free {in_rb_count} clusters: Not allocated"
734 )
735 }
736 Err(err) => event!(Level::WARN, "Failed to free {in_rb_count} clusters: {err}"),
737 }
738
739 count -= ClusterCount(in_rb_count as u64);
740 rb_index = 0;
741 rt_index += 1;
742 }
743 }
744}
745
746#[cfg(test)]
747mod tests {
748 use super::*;
749 use crate::null::Null;
750
751 /// Regression test for a bug in `Allocator::grow_reftable()`'s free-space search loop: when it
752 /// hit a reftable index that had no refblock at all (i.e. a range that has never been
753 /// allocated), it `continue`d without advancing `rt_index` (the increment happened only at the
754 /// bottom of the `while` loop, which the `continue` skipped). If a single iteration wasn't
755 /// enough to satisfy `required_clusters` (which happens once the *total* size of the freshly
756 /// grown reftable reaches or exceeds one refblock's cluster coverage), the loop revisited the
757 /// very same unmapped index again and again, adding its whole refblock-sized range to the
758 /// free-space count each time. This inflated the apparent free space far beyond what was
759 /// actually free starting at that index, so the subsequent "mark allocated" step walked past
760 /// the single truly free refblock-range and right into the next reftable index -- which can be
761 /// a real, already-in-use refblock -- and incremented its refcounts without ever checking they
762 /// were zero. That silently claimed an already-allocated cluster for the new reftable
763 /// structure.
764 #[maybe_async::test(feature = "sync", async(feature = "async", tokio::test))]
765 async fn test_grow_reftable_keeps_in_use_refblock() {
766 // Minimal cluster size and maximal refcount width shrink one refblock's cluster coverage
767 // (and also one reftable cluster's entry count) down to 64, so we don't need a huge image
768 // to reach "new reftable total size >= one refblock's coverage".
769 let cluster_bits = 9u32; // 512-byte clusters
770 let refcount_order = 6u32; // 64-bit refcounts
771 let header = Arc::new(Header::new(cluster_bits, refcount_order, None, None, None));
772 let rb_bits = header.rb_bits();
773 let rb_entries = header.rb_entries();
774 assert_eq!(rb_bits, 6);
775 assert_eq!(rb_entries, 64);
776
777 let null = Arc::new(Null::new(1 << 30));
778 let caches = Arc::new(MetadataCaches::new(&null, &header, 128, 32));
779
780 // We will create two refblocks, one at RT index 4093 (cluster 261952), the other at RT
781 // index 4095 (cluster 262080), the first one completely set (all refcounts 1), the second
782 // one only having its first two entries set (to 1).
783 // Therefore:
784 // - Clusters 261952..=262015 are occupied
785 // - Clusters 262016..=262079 are free
786 // - Clusters 262080..=262081 are occupied
787 // - Rest is free
788 //
789 // The reftable has 4096 entries (to cover everything up to index 4095), thus would need 64
790 // clusters if it were stored anywhere. We ask it to grow to cover index 4096, which
791 // `clone_and_grow()` rounds up to full clusters, i.e. 4160 entries, so 65 clusters, which
792 // does not fit into the 64-cluster long 262016..=262079 range. If we are to put it
793 // at 262082..=262146, then that would go beyond the range of refblock B (262080..=262143),
794 // so we also need a refblock for this range, at RT index 4096. The reftable growth
795 // algorithm puts the new refblocks and the reftable together, so it goes into cluster
796 // 262082, and the reftable starts at cluster 262083 (range 262083..=262147).
797
798 // RB_A: a fully-used refblock at rt_index 4093, where the free-space search starts. It has
799 // no free clusters, so the search legitimately advances to rt_index 4094.
800 let rt_index_a = 4093usize;
801 let rb_a_cluster = HostCluster((rt_index_a * rb_entries) as u64);
802 let mut rb_a = RefBlock::new_cleared(null.as_ref(), &header).unwrap();
803 rb_a.set_cluster(rb_a_cluster);
804 {
805 let mut g = rb_a.lock_write().await;
806 for i in 0..rb_entries {
807 g.increment(i).unwrap();
808 }
809 }
810 let rb_a = Arc::new(rb_a);
811 caches.rb_insert(Arc::clone(&rb_a)).await.unwrap();
812
813 // rt_index 4094 intentionally has no refblock at all: it is a genuinely unmapped, fully
814 // free range.
815
816 // RB_B: the "victim" refblock at rt_index 4095, representing clusters that are already
817 // genuinely allocated (e.g. real guest data/metadata clusters elsewhere in the image).
818 let rt_index_b = 4095usize;
819 let rb_b_cluster = HostCluster((rt_index_b * rb_entries) as u64);
820 let mut rb_b = RefBlock::new_cleared(null.as_ref(), &header).unwrap();
821 rb_b.set_cluster(rb_b_cluster);
822 {
823 let mut g = rb_b.lock_write().await;
824 g.increment(0).unwrap();
825 g.increment(1).unwrap();
826 }
827 let rb_b = Arc::new(rb_b);
828 caches.rb_insert(Arc::clone(&rb_b)).await.unwrap();
829
830 // Build the (old) reftable: entries up to (and including) rt_index_b, with only
831 // rt_index_a and rt_index_b actually mapped to a refblock.
832 let mut data = vec![RefTableEntry::default(); rt_index_b + 1];
833 data[rt_index_a] = <RefTableEntry as TableEntry>::try_from_plain(
834 rb_a_cluster.offset(cluster_bits).0,
835 &header,
836 )
837 .unwrap();
838 data[rt_index_b] = <RefTableEntry as TableEntry>::try_from_plain(
839 rb_b_cluster.offset(cluster_bits).0,
840 &header,
841 )
842 .unwrap();
843 let old_reftable = RefTable::from_data(data.into_boxed_slice(), &header);
844
845 let mut allocator = Allocator {
846 file: Arc::clone(&null),
847 reftable: old_reftable,
848 first_free_cluster: rb_a_cluster,
849 header: Arc::clone(&header),
850 caches: Arc::clone(&caches),
851 clusters_to_free: Arc::new(ClustersToFreeList::default()),
852 };
853
854 // Trigger reftable growth just one index beyond the current bound.
855 allocator.grow_reftable(rt_index_b + 1).await.unwrap();
856
857 // RB_B's clusters were already allocated (refcount 1 each) before the grow, and must
858 // remain untouched by this unrelated reftable-growth operation.
859 assert_eq!(
860 rb_b.get(0),
861 1,
862 "cluster already in use had its refcount corrupted by grow_reftable()"
863 );
864 assert_eq!(
865 rb_b.get(1),
866 1,
867 "cluster already in use had its refcount corrupted by grow_reftable()"
868 );
869
870 // Also verify the reftable is where it is expected, three clusters after the start of what
871 // refblock B describes (the first cluster is B itself, the second is marked as allocated
872 // just like so, and the third is the new refblock at RT index 4096, allocated because the
873 // reftable goes across two refblocks).
874 assert_eq!(
875 allocator.reftable.get_cluster(),
876 Some(HostCluster((rt_index_b * rb_entries + 3) as u64)),
877 "reftable should be placed after refblock B"
878 );
879 }
880}