1use super::*;
4use crate::sync_primitives::RwLockWriteGuard;
5
6#[maybe_async]
7impl<S: Storage, F: WrappedFormat<S>> Qcow2<S, F> {
8 pub(super) async fn do_get_mapping(
12 &self,
13 offset: GuestOffset,
14 max_length: u64,
15 ) -> io::Result<(ShallowMapping<'_, S>, u64)> {
16 let Some(l2_table) = self.get_l2(offset, false).await? else {
17 let cb = self.header.cluster_bits();
18 let len = cmp::min(offset.remaining_in_l2_table(cb), max_length);
19 let mapping = if let Some(backing) = self.backing.as_ref() {
20 ShallowMapping::Indirect {
21 layer: backing.inner(),
22 offset: offset.0,
23 writable: false,
24 }
25 } else {
26 ShallowMapping::Zero { explicit: false }
27 };
28 return Ok((mapping, len));
29 };
30
31 self.do_get_mapping_with_l2(offset, max_length, &l2_table)
32 .await
33 }
34
35 pub(super) async fn do_get_mapping_with_l2(
37 &self,
38 offset: GuestOffset,
39 max_length: u64,
40 l2_table: &L2Table,
41 ) -> io::Result<(ShallowMapping<'_, S>, u64)> {
42 let cb = self.header.cluster_bits();
43
44 let mut current_guest_cluster = offset.cluster(cb);
46 let first_mapping = l2_table.get_mapping(current_guest_cluster)?;
47 let return_mapping = match first_mapping {
48 L2Mapping::DataFile {
49 host_cluster,
50 copied,
51 } => ShallowMapping::Raw {
52 storage: self.storage(),
53 offset: host_cluster.relative_offset(offset, cb).0,
54 writable: copied,
55 },
56
57 L2Mapping::Backing { backing_offset } => {
58 if let Some(backing) = self.backing.as_ref() {
59 ShallowMapping::Indirect {
60 layer: backing.inner(),
61 offset: backing_offset + offset.in_cluster_offset(cb) as u64,
62 writable: false,
63 }
64 } else {
65 ShallowMapping::Zero { explicit: false }
66 }
67 }
68
69 L2Mapping::Zero {
70 host_cluster: _,
71 copied: _,
72 } => ShallowMapping::Zero { explicit: true },
73
74 L2Mapping::Compressed {
75 host_offset: _,
76 length: _,
77 } => ShallowMapping::Special { offset: offset.0 },
78 };
79
80 let mut consecutive_length = offset.remaining_in_cluster(cb);
82 let mut preceding_mapping = first_mapping;
83 while consecutive_length < max_length {
84 let Some(next) = current_guest_cluster.next_in_l2(cb) else {
85 break;
86 };
87 current_guest_cluster = next;
88
89 let mapping = l2_table.get_mapping(current_guest_cluster)?;
90 if !mapping.is_consecutive(&preceding_mapping, cb) {
91 break;
92 }
93
94 preceding_mapping = mapping;
95 consecutive_length += self.header.cluster_size() as u64;
96 }
97
98 consecutive_length = cmp::min(consecutive_length, max_length);
99 Ok((return_mapping, consecutive_length))
100 }
101
102 pub(super) async fn do_prepare_data_mapping(
116 &self,
117 offset: GuestOffset,
118 length: u64,
119 skip_cow: bool,
120 skip_cow_to_eof: bool,
121 ) -> io::Result<(u64, u64, Option<PreparedMappingState>)> {
122 let l2_table = self.ensure_l2(offset).await?;
123
124 let existing = self
131 .do_get_mapping_with_l2(offset, length, &l2_table)
132 .await?;
133 if let ShallowMapping::Raw {
134 storage: _,
135 offset,
136 writable: true,
137 } = existing.0
138 {
139 if existing.1 >= length {
140 return Ok((offset, existing.1, None));
141 }
142 }
143
144 let l2_table = l2_table.lock_write().await;
145
146 let mut l2_changes = Vec::new();
147 let res = self
148 .prepare_data_mapping_no_cleanup(
149 offset,
150 length,
151 skip_cow,
152 skip_cow_to_eof,
153 &l2_table,
154 &mut l2_changes,
155 )
156 .await;
157
158 let (host_offset, length) = match res {
159 Ok(mapped) => mapped,
160 Err(err) => {
161 drop(l2_changes);
163 drop(l2_table);
164 self.process_clusters_to_free().await;
165 return Err(err);
166 }
167 };
168
169 let prepared_mapping = (!l2_changes.is_empty()).then(|| PreparedMappingState {
172 l2_table,
173 l2_changes,
174 clusters_to_free: Arc::clone(&self.clusters_to_free),
175 });
176
177 Ok((host_offset, length, prepared_mapping))
178 }
179
180 pub(super) async fn do_commit_data_mapping(&self, state: PreparedMappingState) {
184 let PreparedMappingState {
185 mut l2_table,
186 l2_changes,
187 clusters_to_free: _,
188 } = state;
189
190 for (l2_index, host_cluster, new_allocation) in l2_changes {
191 if let Some(leaked) = l2_table.map_cluster(l2_index, host_cluster, new_allocation) {
192 self.clusters_to_free.push(leaked);
193 }
194 }
195 drop(l2_table);
196
197 self.process_clusters_to_free().await;
198 }
199
200 pub(super) async fn ensure_fixed_mapping(
206 &self,
207 offset: GuestOffset,
208 length: u64,
209 mapping: FixedMapping,
210 ) -> io::Result<(GuestOffset, u64)> {
211 match mapping {
212 FixedMapping::ZeroDiscard | FixedMapping::ZeroRetainAllocation => {
213 self.header.require_version(3)?;
214 }
215 FixedMapping::FullDiscard => (),
216 }
217
218 let cb = self.header.cluster_bits();
219
220 let cluster_align_mask = self.header.cluster_size() as u64 - 1;
222 let end = (offset + length).0;
223 let aligned_end = if end == self.header.size() {
224 (end + cluster_align_mask) & !cluster_align_mask
227 } else {
228 end & !cluster_align_mask
230 };
231 let aligned_offset = (offset + cluster_align_mask).0 & !cluster_align_mask;
232 let aligned_length = aligned_end.saturating_sub(aligned_offset);
233
234 let first_cluster = GuestOffset(aligned_offset).checked_cluster(cb).unwrap();
236 let cluster_count = ClusterCount::checked_from_byte_size(aligned_length, cb).unwrap();
237
238 if cluster_count.0 == 0 {
239 return Ok((GuestOffset(aligned_offset), 0));
240 }
241
242 let l2_table = self.ensure_l2(first_cluster.offset(cb)).await?;
243 let l2_table = l2_table.lock_write().await;
244
245 let res = self
246 .ensure_fixed_mapping_no_cleanup(first_cluster, cluster_count, mapping, l2_table)
247 .await;
248
249 self.process_clusters_to_free().await;
250
251 let count = res?;
252
253 let affected_offset = first_cluster.offset(cb);
254 let affected_length = count.byte_size(cb);
255
256 let head = affected_offset - offset;
257 let affected_length = cmp::min(affected_length, length.saturating_sub(head));
260
261 Ok((affected_offset, affected_length))
262 }
263
264 pub(super) async fn get_l2(
271 &self,
272 offset: GuestOffset,
273 writable: bool,
274 ) -> io::Result<Option<Arc<L2Table>>> {
275 let cb = self.header.cluster_bits();
276
277 let l1_entry = self.l1_table.read().await.get(offset.l1_index(cb));
278 if let Some(l2_offset) = l1_entry.l2_offset() {
279 if writable && !l1_entry.is_copied() {
280 return Ok(None);
281 }
282 let l2_cluster = l2_offset.checked_cluster(cb).ok_or_else(|| {
283 invalid_data(format!(
284 "Unaligned L2 table for {offset:?}; L1 entry: {l1_entry:?}"
285 ))
286 })?;
287
288 self.caches.l2_get_or_insert(l2_cluster).await.map(Some)
289 } else {
290 Ok(None)
291 }
292 }
293
294 pub(super) async fn ensure_l2(&self, offset: GuestOffset) -> io::Result<Arc<L2Table>> {
299 let cb = self.header.cluster_bits();
300
301 if let Some(l2) = self.get_l2(offset, true).await? {
302 return Ok(l2);
303 }
304
305 self.need_writable()?;
306
307 let mut l1_locked = self.l1_table.write().await;
308 let l1_index = offset.l1_index(cb);
309 if !l1_locked.in_bounds(l1_index) {
310 l1_locked = self.grow_l1_table(l1_locked, l1_index).await?;
311 }
312
313 let l1_entry = l1_locked.get(l1_index);
314 let mut l2_table = if let Some(l2_offset) = l1_entry.l2_offset() {
315 let l2_cluster = l2_offset.checked_cluster(cb).ok_or_else(|| {
316 invalid_data(format!(
317 "Unaligned L2 table for {offset:?}; L1 entry: {l1_entry:?}"
318 ))
319 })?;
320
321 let l2 = self.caches.l2_get_or_insert(l2_cluster).await?;
322 if l1_entry.is_copied() {
323 return Ok(l2);
324 }
325
326 L2Table::clone(&l2)
327 } else {
328 L2Table::new_cleared(&self.header)
329 };
330
331 let l2_allocation = self.allocate_meta_cluster().await?;
332 l2_table.set_cluster(l2_allocation.cluster());
333 l2_table.write(self.metadata.as_ref()).await?;
334
335 l1_locked.enter_l2_table(l1_index, &l2_table, Some(l2_allocation))?;
336 l1_locked
337 .write_entry(self.metadata.as_ref(), l1_index)
338 .await?;
339
340 if let Some(l2_offset) = l1_entry.l2_offset() {
342 self.clusters_to_free.push(ClustersToFreeEntry::metadata(
343 l2_offset.cluster(cb),
344 ClusterCount(1),
345 ));
346 self.process_clusters_to_free().await;
347 }
348
349 let l2_table = Arc::new(l2_table);
350 self.caches.l2_insert(Arc::clone(&l2_table)).await?;
351 Ok(l2_table)
352 }
353
354 pub(super) async fn grow_l1_table<'a>(
358 &self,
359 mut l1_locked: RwLockWriteGuard<'a, L1Table>,
360 at_least_index: usize,
361 ) -> io::Result<RwLockWriteGuard<'a, L1Table>> {
362 let mut new_l1 = l1_locked.clone_and_grow(at_least_index, &self.header)?;
363
364 let l1_allocation = self.allocate_meta_clusters(new_l1.cluster_count()).await?;
365
366 new_l1.set_cluster(l1_allocation.cluster());
367 new_l1.write(self.metadata.as_ref()).await?;
368
369 self.header.set_l1_table(&new_l1, Some(l1_allocation))?;
370 self.header
371 .write_l1_table_pointer(self.metadata.as_ref())
372 .await?;
373
374 if let Some(old_l1_cluster) = l1_locked.get_cluster() {
375 let old_l1_size = l1_locked.cluster_count();
376 l1_locked.unset_cluster();
377 self.clusters_to_free
378 .push(ClustersToFreeEntry::metadata(old_l1_cluster, old_l1_size));
379 self.process_clusters_to_free().await;
380 }
381
382 *l1_locked = new_l1;
383
384 Ok(l1_locked)
385 }
386
387 async fn prepare_data_mapping_no_cleanup(
396 &self,
397 offset: GuestOffset,
398 full_length: u64,
399 skip_cow: bool,
400 skip_cow_to_eof: bool,
401 l2_table: &L2TableWriteGuard,
402 l2_changes: &mut Vec<(usize, HostCluster, Option<PendingClusters>)>,
403 ) -> io::Result<(u64, u64)> {
404 let cb = self.header.cluster_bits();
405
406 let partial_skip_cow = skip_cow.then(|| {
407 let start = offset.in_cluster_offset(cb);
408 let end = if skip_cow_to_eof {
409 1 << cb
410 } else {
411 cmp::min(start as u64 + full_length, 1 << cb) as usize
412 };
413 start..end
414 });
415
416 let mut current_guest_cluster = offset.cluster(cb);
417
418 let prepared = self
420 .prepare_cow_cluster(current_guest_cluster, None, partial_skip_cow, l2_table)
421 .await?
422 .ok_or_else(|| io::Error::other("Internal allocation error"))?;
423 let host_cluster = prepared.host_cluster;
424 if let Some((l2_index, new_allocation)) = prepared.set_mapping {
425 l2_changes.push((l2_index, host_cluster, new_allocation));
426 }
427
428 let host_offset_start = host_cluster.relative_offset(offset, cb);
429 let mut allocated_length = offset.remaining_in_cluster(cb);
430 let mut current_host_cluster = host_cluster;
431
432 while allocated_length < full_length {
433 let Some(next) = current_guest_cluster.next_in_l2(cb) else {
434 break;
435 };
436 current_guest_cluster = next;
437
438 let chunk_length = cmp::min(full_length - allocated_length, 1 << cb) as usize;
439 let partial_skip_cow = match (skip_cow, skip_cow_to_eof) {
440 (false, _) => None,
441 (true, false) => Some(0..chunk_length),
442 (true, true) => Some(0..(1 << cb)),
443 };
444
445 let next_host_cluster = current_host_cluster + ClusterCount(1);
446 let prepared = self
447 .prepare_cow_cluster(
448 current_guest_cluster,
449 Some(next_host_cluster),
450 partial_skip_cow,
451 l2_table,
452 )
453 .await?;
454
455 let Some(prepared) = prepared else {
456 break;
458 };
459 let host_cluster = prepared.host_cluster;
460 assert!(host_cluster == next_host_cluster);
461 if let Some((l2_index, new_allocation)) = prepared.set_mapping {
462 l2_changes.push((l2_index, host_cluster, new_allocation));
463 }
464 current_host_cluster = host_cluster;
465
466 allocated_length += chunk_length as u64;
467 }
468
469 Ok((host_offset_start.0, allocated_length))
470 }
471
472 async fn ensure_fixed_mapping_no_cleanup(
482 &self,
483 first_cluster: GuestCluster,
484 count: ClusterCount,
485 mapping: FixedMapping,
486 mut l2_table: L2TableWriteGuard,
487 ) -> io::Result<ClusterCount> {
488 let cb = self.header.cluster_bits();
489 let mut cluster = first_cluster;
490 let end_cluster = first_cluster + count;
491 let mut done = ClusterCount(0);
492
493 while cluster < end_cluster {
494 let l2i = cluster.l2_index(cb);
495 let leaked = match mapping {
496 FixedMapping::ZeroDiscard => l2_table.zero_cluster(l2i, false)?,
497 FixedMapping::ZeroRetainAllocation => l2_table.zero_cluster(l2i, true)?,
498 FixedMapping::FullDiscard => l2_table.discard_cluster(l2i),
499 };
500 if let Some(leaked) = leaked {
501 self.clusters_to_free.push(leaked);
502 }
503
504 done += ClusterCount(1);
505 let Some(next) = cluster.next_in_l2(cb) else {
506 break;
507 };
508 cluster = next;
509 }
510
511 Ok(done)
512 }
513}
514
515pub(super) struct PreparedMappingState {
523 l2_table: L2TableWriteGuard,
534
535 l2_changes: Vec<(usize, HostCluster, Option<PendingClusters>)>,
538
539 clusters_to_free: Arc<ClustersToFreeList>,
541}
542
543impl PreparedMappingState {
544 pub fn belongs_to<S: Storage + 'static, F: WrappedFormat<S> + 'static>(
546 &self,
547 qcow2: &Qcow2<S, F>,
548 ) -> bool {
549 Arc::ptr_eq(&self.clusters_to_free, &qcow2.clusters_to_free)
550 }
551}
552
553#[derive(Clone, Copy, Debug, Eq, PartialEq)]
555pub(super) enum FixedMapping {
556 ZeroDiscard,
561
562 ZeroRetainAllocation,
571
572 FullDiscard,
577}
578
579#[cfg(test)]
580mod tests {
581 use super::*;
584 use crate::format::drivers::FormatDriverInstance;
585 use crate::test::mem_storage::{MemStorage, MemStorageState};
586 use crate::{DenyImplicitOpenGate, Mapping};
587
588 #[maybe_async]
591 async fn create_image(
592 state: &Arc<MemStorageState>,
593 size: u64,
594 refcount_width: usize,
595 ) -> Qcow2<MemStorage> {
596 Qcow2::<MemStorage>::create_builder(MemStorage::new(Arc::clone(state), false))
597 .cluster_size(512)
598 .refcount_width(refcount_width)
599 .size(size)
600 .create_open(DenyImplicitOpenGate::default(), |image| {
601 Ok(Qcow2::<MemStorage>::builder(image)
602 .backing(None)
603 .write(true))
604 })
605 .await
606 .unwrap()
607 }
608
609 #[cfg(feature = "async")]
611 #[maybe_async]
612 async fn load_reftable(qcow2: &Qcow2<MemStorage>) -> RefTable {
613 let cb = qcow2.header.cluster_bits();
614 let rt_cluster = qcow2.header.reftable_offset().checked_cluster(cb).unwrap();
615 RefTable::load(
616 qcow2.metadata.as_ref(),
617 &qcow2.header,
618 rt_cluster,
619 qcow2.header.reftable_entries(),
620 )
621 .await
622 .unwrap()
623 }
624
625 #[cfg(feature = "async")]
628 #[tokio::test]
629 async fn test_concurrent_l2_writeback() {
630 use futures::task::noop_waker;
631 use std::future::Future;
632 use std::sync::atomic::Ordering;
633 use std::task::{Context, Poll};
634
635 let state = Arc::new(MemStorageState::default());
636 let qcow2 = create_image(&state, 64 * 1024, 16).await;
637 let cb = qcow2.header.cluster_bits();
638
639 qcow2.ensure_data_mapping(0, 1024, true).await.unwrap();
641 qcow2.flush().await.unwrap();
642
643 qcow2.ensure_data_mapping(1024, 512, true).await.unwrap();
645
646 let l2_cluster = {
647 let l1 = qcow2.l1_table.read().await;
648 l1.get(GuestOffset(0).l1_index(cb))
649 .l2_offset()
650 .unwrap()
651 .checked_cluster(cb)
652 .unwrap()
653 };
654 let l2_offset = l2_cluster.offset(cb).0;
655
656 let waker = noop_waker();
657 let mut cx = Context::from_waker(&waker);
658
659 *state.hold_range.lock() = Some(l2_offset..l2_offset + 512);
661 state.hold_writes.store(true, Ordering::Relaxed);
662 let mut flush_a = Box::pin(qcow2.flush());
663 assert!(flush_a.as_mut().poll(&mut cx).is_pending());
664 assert_eq!(state.held.lock().len(), 1);
665 state.hold_writes.store(false, Ordering::Relaxed);
666
667 qcow2.ensure_zero_mapping(512, 512).await.unwrap();
669
670 let mut flush_b = Box::pin(qcow2.flush());
672 let flush_b_result = match flush_b.as_mut().poll(&mut cx) {
673 Poll::Ready(result) => Some(result),
674 Poll::Pending => None,
675 };
676
677 state.release_held_writes();
678 let Poll::Ready(result) = flush_a.as_mut().poll(&mut cx) else {
679 panic!("Flush A did not complete");
680 };
681 result.unwrap();
682 match flush_b_result {
683 Some(result) => result.unwrap(),
684 None => flush_b.await.unwrap(),
685 }
686
687 let entry_offset = (l2_offset + 8) as usize;
689 let data = state.data.lock();
690 let entry = u64::from_be_bytes(data[entry_offset..entry_offset + 8].try_into().unwrap());
691 assert!(
692 entry & 1 != 0,
693 "zeroing guest cluster 1 lost on disk (L2 entry {entry:#x})"
694 );
695 }
696
697 #[cfg(feature = "async")]
700 #[tokio::test]
701 async fn test_concurrent_refblock_writeback() {
702 use futures::task::noop_waker;
703 use std::future::Future;
704 use std::sync::atomic::Ordering;
705 use std::task::{Context, Poll};
706
707 let state = Arc::new(MemStorageState::default());
708 let qcow2 = create_image(&state, 64 * 1024, 16).await;
709 let rb_bits = qcow2.header.rb_bits();
710 let refcount_bytes = 2; qcow2.flush().await.unwrap();
712
713 let first_pending = qcow2.allocate_meta_cluster().await.unwrap();
715 let first = first_pending.cluster();
716 first_pending.make_persistent();
717 assert_eq!(first.rt_index(rb_bits), 0);
718 let rb_offset = load_reftable(&qcow2)
719 .await
720 .get(0)
721 .refblock_offset()
722 .unwrap()
723 .0;
724
725 let waker = noop_waker();
726 let mut cx = Context::from_waker(&waker);
727
728 *state.hold_range.lock() = Some(rb_offset..rb_offset + 512);
730 state.hold_writes.store(true, Ordering::Relaxed);
731 let mut flush_a = Box::pin(qcow2.flush());
732 assert!(flush_a.as_mut().poll(&mut cx).is_pending());
733 assert_eq!(state.held.lock().len(), 1);
734 state.hold_writes.store(false, Ordering::Relaxed);
735
736 let second_pending = qcow2.allocate_meta_cluster().await.unwrap();
738 let second = second_pending.cluster();
739 second_pending.make_persistent();
740 assert_eq!(second.rt_index(rb_bits), 0);
741
742 let mut flush_b = Box::pin(qcow2.flush());
744 let flush_b_result = match flush_b.as_mut().poll(&mut cx) {
745 Poll::Ready(result) => Some(result),
746 Poll::Pending => None,
747 };
748
749 state.release_held_writes();
750 let Poll::Ready(result) = flush_a.as_mut().poll(&mut cx) else {
751 panic!("Flush A did not complete");
752 };
753 result.unwrap();
754 match flush_b_result {
755 Some(result) => result.unwrap(),
756 None => flush_b.await.unwrap(),
757 }
758
759 let refcount_offset =
761 (rb_offset + (second.rb_index(rb_bits) * refcount_bytes) as u64) as usize;
762 let data = state.data.lock();
763 let refcount = u16::from_be_bytes(
764 data[refcount_offset..refcount_offset + refcount_bytes]
765 .try_into()
766 .unwrap(),
767 );
768 assert_eq!(
769 refcount, 1,
770 "refcount increment for {second:?} lost on disk"
771 );
772 }
773
774 #[maybe_async::test(feature = "sync", async(feature = "async", tokio::test))]
777 async fn test_full_discard_v2() {
778 let state = Arc::new(MemStorageState::default());
782 let qcow2 = create_image(&state, 64 * 1024, 16).await;
783 qcow2.flush().await.unwrap();
784 drop(qcow2);
785 state.data.lock()[4..8].copy_from_slice(&2u32.to_be_bytes());
786
787 let qcow2 = Qcow2::<MemStorage>::builder(MemStorage::new(state, false))
788 .backing(None)
789 .write(true)
790 .open(DenyImplicitOpenGate::default())
791 .await
792 .unwrap();
793 qcow2.header.require_version(2).unwrap();
794 qcow2.header.require_version(3).unwrap_err();
795 let mut image = FormatAccess::new(qcow2);
796
797 image.write(&[0x11u8; 512][..], 0).await.unwrap();
798 let (
799 Mapping::Raw {
800 offset: discarded_offset,
801 ..
802 },
803 _,
804 ) = image.get_mapping(0, 512).await.unwrap()
805 else {
806 panic!("Expected a data mapping");
807 };
808
809 image.discard_to_backing(0, 512).await.unwrap();
810 let (mapping, _) = image.get_mapping(0, 512).await.unwrap();
811 assert!(
812 !matches!(mapping, Mapping::Raw { .. }),
813 "Discarded cluster still mapped: {mapping:?}"
814 );
815
816 image.write(&[0x22u8; 512][..], 512).await.unwrap();
818 let (Mapping::Raw { offset, .. }, _) = image.get_mapping(512, 512).await.unwrap() else {
819 panic!("Expected a data mapping");
820 };
821 assert_eq!(offset, discarded_offset);
822 }
823
824 #[maybe_async::test(feature = "sync", async(feature = "async", tokio::test))]
826 async fn test_mapping_beyond_disk_size() {
827 let size = 64 * 1024;
828 let qcow2 = create_image(&Arc::default(), size, 16).await;
829 let length = size + 512;
830
831 let check = |result: io::Result<_>, req| match result {
832 Ok(_) => panic!("{req} of {length} bytes at 0 beyond the disk size ({size}) succeeded"),
833 Err(err) => assert_eq!(err.kind(), io::ErrorKind::UnexpectedEof, "{req}: {err}"),
834 };
835
836 check(
837 qcow2
838 .ensure_data_mapping(0, length, false)
839 .await
840 .map(|_| ()),
841 "ensure_data_mapping",
842 );
843 check(
844 qcow2.ensure_zero_mapping(0, length).await.map(|_| ()),
845 "ensure_zero_mapping",
846 );
847 check(
849 unsafe { qcow2.discard_to_zero_unsafe(0, length) }
850 .await
851 .map(|_| ()),
852 "discard_to_zero_unsafe",
853 );
854 check(
856 unsafe { qcow2.discard_to_backing_unsafe(0, length) }
857 .await
858 .map(|_| ()),
859 "discard_to_backing_unsafe",
860 );
861 }
862
863 #[maybe_async::test(feature = "sync", async(feature = "async", tokio::test))]
866 async fn test_prepare_commit_data_mapping() {
867 let state = Arc::new(MemStorageState::default());
868 let image = FormatAccess::new(create_image(&state, 64 * 1024, 16).await);
869 let mut buf = [0xffu8; 512];
870
871 image.write(&[0x11u8; 512][..], 0).await.unwrap();
873
874 let mapping = image.prepare_data_mapping(512, 512, true).await.unwrap();
876 assert!(!mapping.is_nonblocking());
877 assert_eq!(mapping.length(), 512);
878 let host_offset = mapping.offset();
879 mapping
880 .storage()
881 .write(&[0x22u8; 512][..], host_offset)
882 .await
883 .unwrap();
884
885 image.read(&mut buf[..], 512).await.unwrap();
886 assert!(buf == [0u8; 512], "Uncommitted mapping is visible");
887
888 drop(mapping);
889 image.read(&mut buf[..], 512).await.unwrap();
890 assert!(buf == [0u8; 512], "Dropped mapping is visible");
891
892 image.flush().await.unwrap();
895 let mapping = image.prepare_data_mapping(512, 512, true).await.unwrap();
896 assert!(!mapping.is_nonblocking());
897 assert_eq!(mapping.offset(), host_offset);
898 mapping
899 .storage()
900 .write(&[0x33u8; 512][..], host_offset)
901 .await
902 .unwrap();
903 image.commit_data_mapping(mapping).await.unwrap();
904
905 image.read(&mut buf[..], 512).await.unwrap();
906 assert!(buf == [0x33u8; 512], "Committed mapping is not visible");
907
908 let mapping = image.prepare_data_mapping(512, 512, true).await.unwrap();
910 assert!(mapping.is_nonblocking());
911 assert_eq!(mapping.offset(), host_offset);
912 image.commit_data_mapping(mapping).await.unwrap();
913 }
914
915 #[maybe_async::test(feature = "sync", async(feature = "async", tokio::test))]
917 async fn test_commit_data_mapping_on_other_image() {
918 let state = Arc::new(MemStorageState::default());
919 let image = FormatAccess::new(create_image(&state, 64 * 1024, 16).await);
920 let other_state = Arc::new(MemStorageState::default());
921 let other_image = FormatAccess::new(create_image(&other_state, 64 * 1024, 16).await);
922
923 let mapping = image.prepare_data_mapping(0, 512, true).await.unwrap();
924 assert!(!mapping.is_nonblocking());
925 let err = other_image.commit_data_mapping(mapping).await.unwrap_err();
926 assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
927
928 for image in [&image, &other_image] {
930 let (mapping, _) = image.get_mapping(0, 512).await.unwrap();
931 assert!(
932 matches!(mapping, Mapping::Zero { explicit: false }),
933 "Cluster has been mapped"
934 );
935 }
936 }
937
938 #[cfg(feature = "async")]
940 fn read_u64(state: &MemStorageState, offset: u64) -> u64 {
941 let data = state.data.lock();
942 let offset = offset as usize;
943 u64::from_be_bytes(data[offset..offset + 8].try_into().unwrap())
944 }
945
946 #[cfg(feature = "async")]
949 #[tokio::test]
950 async fn test_dropped_l2_writeback_keeps_table_modified() {
951 use crate::StorageExt;
952 use futures::FutureExt;
953
954 let state = Arc::new(MemStorageState::default());
955 let qcow2 = create_image(&state, 64 * 1024, 16).await;
956 let cb = qcow2.header.cluster_bits();
957
958 let mapping = qcow2.prepare_data_mapping(0, 512, true).await.unwrap();
960 qcow2.commit_data_mapping(mapping).await.unwrap();
961 qcow2.flush().await.unwrap();
962
963 let mapping = qcow2.prepare_data_mapping(512, 512, true).await.unwrap();
965 let host_offset = mapping.offset();
966 qcow2.commit_data_mapping(mapping).await.unwrap();
967
968 let l2_offset = {
969 let l1 = qcow2.l1_table.read().await;
970 l1.get(GuestOffset(0).l1_index(cb)).l2_offset().unwrap().0
971 };
972
973 let blocker = qcow2
976 .metadata
977 .strong_write_blocker(l2_offset..(l2_offset + 512))
978 .await;
979 assert!(qcow2.flush().now_or_never().is_none());
980 drop(blocker);
981
982 qcow2.flush().await.unwrap();
984 let entry = read_u64(&state, l2_offset + 8);
985 assert_eq!(
986 entry & 0x00ff_ffff_ffff_fe00,
987 host_offset,
988 "mapping guest cluster 1 lost on disk (L2 entry {entry:#x})"
989 );
990 }
991
992 #[cfg(feature = "async")]
995 #[tokio::test]
996 async fn test_dropped_refblock_writeback_keeps_block_modified() {
997 use crate::StorageExt;
998 use futures::FutureExt;
999
1000 let state = Arc::new(MemStorageState::default());
1001 let qcow2 = create_image(&state, 64 * 1024, 16).await;
1002 let cb = qcow2.header.cluster_bits();
1003 let rb_bits = qcow2.header.rb_bits();
1004 let refcount_bytes = 2; let mapping = qcow2.prepare_data_mapping(0, 512, true).await.unwrap();
1008 qcow2.commit_data_mapping(mapping).await.unwrap();
1009 qcow2.flush().await.unwrap();
1010
1011 let mapping = qcow2.prepare_data_mapping(512, 512, true).await.unwrap();
1013 let host_offset = mapping.offset();
1014 qcow2.commit_data_mapping(mapping).await.unwrap();
1015 let host_cluster = HostOffset(host_offset).cluster(cb);
1016 assert_eq!(host_cluster.rt_index(rb_bits), 0);
1017
1018 let rb_offset = read_u64(&state, qcow2.header.reftable_offset().0) & !511;
1019
1020 let blocker = qcow2
1023 .metadata
1024 .strong_write_blocker(rb_offset..(rb_offset + 512))
1025 .await;
1026 assert!(qcow2.flush().now_or_never().is_none());
1027 drop(blocker);
1028
1029 qcow2.flush().await.unwrap();
1031 let refcount_offset =
1032 (rb_offset + (host_cluster.rb_index(rb_bits) * refcount_bytes) as u64) as usize;
1033 let data = state.data.lock();
1034 let refcount = u16::from_be_bytes(
1035 data[refcount_offset..refcount_offset + refcount_bytes]
1036 .try_into()
1037 .unwrap(),
1038 );
1039 assert_eq!(
1040 refcount, 1,
1041 "refcount increment for {host_cluster:?} lost on disk"
1042 );
1043 }
1044}