Compress every allocated cluster in a QCOW2 image file in place. Walks L1 -> L2 tables, compresses each standard cluster with raw deflate, appends the compressed payload at the end of the file, and rewrites the L2 entry with the compressed layout.
(file: &mut File)
| 333 | /// deflate, appends the compressed payload at the end of the file, |
| 334 | /// and rewrites the L2 entry with the compressed layout. |
| 335 | pub fn compress_allocated_clusters(file: &mut File) { |
| 336 | file.seek(SeekFrom::Start(HEADER_CLUSTER_BITS_OFFSET)) |
| 337 | .unwrap(); |
| 338 | let cluster_bits = file.read_u32::<BigEndian>().unwrap(); |
| 339 | let cluster_size = 1u64 << cluster_bits; |
| 340 | |
| 341 | file.seek(SeekFrom::Start(HEADER_L1_SIZE_OFFSET)).unwrap(); |
| 342 | let l1_size = file.read_u32::<BigEndian>().unwrap(); |
| 343 | |
| 344 | file.seek(SeekFrom::Start(HEADER_L1_TABLE_OFFSET)).unwrap(); |
| 345 | let l1_table_offset = file.read_u64::<BigEndian>().unwrap(); |
| 346 | |
| 347 | let entries_per_l2 = cluster_size / 8; |
| 348 | |
| 349 | let mut append_offset = file.seek(SeekFrom::End(0)).unwrap(); |
| 350 | append_offset = (append_offset + 511) & !511; |
| 351 | |
| 352 | for l1_idx in 0..l1_size as u64 { |
| 353 | let l1_entry_offset = l1_table_offset + l1_idx * 8; |
| 354 | file.seek(SeekFrom::Start(l1_entry_offset)).unwrap(); |
| 355 | let l1_entry = file.read_u64::<BigEndian>().unwrap(); |
| 356 | |
| 357 | let l2_table_addr = l1_entry & L1_L2_ADDR_MASK; |
| 358 | if l2_table_addr == 0 { |
| 359 | continue; |
| 360 | } |
| 361 | |
| 362 | for l2_idx in 0..entries_per_l2 { |
| 363 | let l2_entry_offset = l2_table_addr + l2_idx * 8; |
| 364 | file.seek(SeekFrom::Start(l2_entry_offset)).unwrap(); |
| 365 | let l2_entry = file.read_u64::<BigEndian>().unwrap(); |
| 366 | |
| 367 | if l2_entry & CLUSTER_USED_FLAG == 0 || l2_entry & COMPRESSED_FLAG != 0 { |
| 368 | continue; |
| 369 | } |
| 370 | |
| 371 | let host_cluster_addr = l2_entry & L1_L2_ADDR_MASK; |
| 372 | if host_cluster_addr == 0 { |
| 373 | continue; |
| 374 | } |
| 375 | |
| 376 | let mut cluster_data = vec![0u8; cluster_size as usize]; |
| 377 | file.seek(SeekFrom::Start(host_cluster_addr)).unwrap(); |
| 378 | file.read_exact(&mut cluster_data).unwrap(); |
| 379 | |
| 380 | let mut encoder = DeflateEncoder::new(Vec::new(), Compression::default()); |
| 381 | encoder.write_all(&cluster_data).unwrap(); |
| 382 | let compressed = encoder.finish().unwrap(); |
| 383 | |
| 384 | file.seek(SeekFrom::Start(append_offset)).unwrap(); |
| 385 | file.write_all(&compressed).unwrap(); |
| 386 | |
| 387 | // The L2 entry encodes the compressed size in units of |
| 388 | // 512 byte sectors. The reader decodes the sector count |
| 389 | // back and computes: nsectors * 512 - (addr & 511). |
| 390 | // Because addr is 512 aligned, this yields nsectors * 512 |
| 391 | // which rounds up to the next sector boundary. The file |
| 392 | // must contain enough bytes for that rounded up pread. |