xref: /aosp_15_r20/external/crosvm/disk/src/zstd.rs (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
1*bb4ee6a4SAndroid Build Coastguard Worker // Copyright 2024 The ChromiumOS Authors
2*bb4ee6a4SAndroid Build Coastguard Worker // Use of this source code is governed by a BSD-style license that can be
3*bb4ee6a4SAndroid Build Coastguard Worker // found in the LICENSE file.
4*bb4ee6a4SAndroid Build Coastguard Worker 
5*bb4ee6a4SAndroid Build Coastguard Worker //! Use seekable zstd archive of raw disk image as read only disk
6*bb4ee6a4SAndroid Build Coastguard Worker 
7*bb4ee6a4SAndroid Build Coastguard Worker use std::cmp::min;
8*bb4ee6a4SAndroid Build Coastguard Worker use std::fs::File;
9*bb4ee6a4SAndroid Build Coastguard Worker use std::io;
10*bb4ee6a4SAndroid Build Coastguard Worker use std::io::ErrorKind;
11*bb4ee6a4SAndroid Build Coastguard Worker use std::io::Read;
12*bb4ee6a4SAndroid Build Coastguard Worker use std::io::Seek;
13*bb4ee6a4SAndroid Build Coastguard Worker use std::sync::Arc;
14*bb4ee6a4SAndroid Build Coastguard Worker 
15*bb4ee6a4SAndroid Build Coastguard Worker use anyhow::bail;
16*bb4ee6a4SAndroid Build Coastguard Worker use anyhow::Context;
17*bb4ee6a4SAndroid Build Coastguard Worker use async_trait::async_trait;
18*bb4ee6a4SAndroid Build Coastguard Worker use base::AsRawDescriptor;
19*bb4ee6a4SAndroid Build Coastguard Worker use base::FileAllocate;
20*bb4ee6a4SAndroid Build Coastguard Worker use base::FileReadWriteAtVolatile;
21*bb4ee6a4SAndroid Build Coastguard Worker use base::FileSetLen;
22*bb4ee6a4SAndroid Build Coastguard Worker use base::RawDescriptor;
23*bb4ee6a4SAndroid Build Coastguard Worker use base::VolatileSlice;
24*bb4ee6a4SAndroid Build Coastguard Worker use cros_async::BackingMemory;
25*bb4ee6a4SAndroid Build Coastguard Worker use cros_async::Executor;
26*bb4ee6a4SAndroid Build Coastguard Worker use cros_async::IoSource;
27*bb4ee6a4SAndroid Build Coastguard Worker 
28*bb4ee6a4SAndroid Build Coastguard Worker use crate::AsyncDisk;
29*bb4ee6a4SAndroid Build Coastguard Worker use crate::DiskFile;
30*bb4ee6a4SAndroid Build Coastguard Worker use crate::DiskGetLen;
31*bb4ee6a4SAndroid Build Coastguard Worker use crate::Error as DiskError;
32*bb4ee6a4SAndroid Build Coastguard Worker use crate::Result as DiskResult;
33*bb4ee6a4SAndroid Build Coastguard Worker use crate::ToAsyncDisk;
34*bb4ee6a4SAndroid Build Coastguard Worker 
35*bb4ee6a4SAndroid Build Coastguard Worker // Zstandard frame magic
36*bb4ee6a4SAndroid Build Coastguard Worker pub const ZSTD_FRAME_MAGIC: u32 = 0xFD2FB528;
37*bb4ee6a4SAndroid Build Coastguard Worker 
38*bb4ee6a4SAndroid Build Coastguard Worker // Skippable frame magic can be anything between [0x184D2A50, 0x184D2A5F]
39*bb4ee6a4SAndroid Build Coastguard Worker pub const ZSTD_SKIPPABLE_MAGIC_LOW: u32 = 0x184D2A50;
40*bb4ee6a4SAndroid Build Coastguard Worker pub const ZSTD_SKIPPABLE_MAGIC_HIGH: u32 = 0x184D2A5F;
41*bb4ee6a4SAndroid Build Coastguard Worker pub const ZSTD_SEEK_TABLE_MAGIC: u32 = 0x8F92EAB1;
42*bb4ee6a4SAndroid Build Coastguard Worker 
43*bb4ee6a4SAndroid Build Coastguard Worker pub const ZSTD_DEFAULT_FRAME_SIZE: usize = 128 << 10; // 128KB
44*bb4ee6a4SAndroid Build Coastguard Worker 
45*bb4ee6a4SAndroid Build Coastguard Worker #[derive(Clone, Debug)]
46*bb4ee6a4SAndroid Build Coastguard Worker pub struct ZstdSeekTable {
47*bb4ee6a4SAndroid Build Coastguard Worker     // Cumulative sum of decompressed sizes of all frames before the indexed frame.
48*bb4ee6a4SAndroid Build Coastguard Worker     // The last element is the total decompressed size of the zstd archive.
49*bb4ee6a4SAndroid Build Coastguard Worker     cumulative_decompressed_sizes: Vec<u64>,
50*bb4ee6a4SAndroid Build Coastguard Worker     // Cumulative sum of compressed sizes of all frames before the indexed frame.
51*bb4ee6a4SAndroid Build Coastguard Worker     // The last element is the total compressed size of the zstd archive.
52*bb4ee6a4SAndroid Build Coastguard Worker     cumulative_compressed_sizes: Vec<u64>,
53*bb4ee6a4SAndroid Build Coastguard Worker }
54*bb4ee6a4SAndroid Build Coastguard Worker 
55*bb4ee6a4SAndroid Build Coastguard Worker impl ZstdSeekTable {
56*bb4ee6a4SAndroid Build Coastguard Worker     /// Read seek table entries from seek_table_entries
from_footer( seek_table_entries: &[u8], num_frames: u32, checksum_flag: bool, ) -> anyhow::Result<ZstdSeekTable>57*bb4ee6a4SAndroid Build Coastguard Worker     pub fn from_footer(
58*bb4ee6a4SAndroid Build Coastguard Worker         seek_table_entries: &[u8],
59*bb4ee6a4SAndroid Build Coastguard Worker         num_frames: u32,
60*bb4ee6a4SAndroid Build Coastguard Worker         checksum_flag: bool,
61*bb4ee6a4SAndroid Build Coastguard Worker     ) -> anyhow::Result<ZstdSeekTable> {
62*bb4ee6a4SAndroid Build Coastguard Worker         let mut cumulative_decompressed_size: u64 = 0;
63*bb4ee6a4SAndroid Build Coastguard Worker         let mut cumulative_compressed_size: u64 = 0;
64*bb4ee6a4SAndroid Build Coastguard Worker         let mut cumulative_decompressed_sizes = Vec::with_capacity(num_frames as usize + 1);
65*bb4ee6a4SAndroid Build Coastguard Worker         let mut cumulative_compressed_sizes = Vec::with_capacity(num_frames as usize + 1);
66*bb4ee6a4SAndroid Build Coastguard Worker         let mut offset = 0;
67*bb4ee6a4SAndroid Build Coastguard Worker         cumulative_decompressed_sizes.push(0);
68*bb4ee6a4SAndroid Build Coastguard Worker         cumulative_compressed_sizes.push(0);
69*bb4ee6a4SAndroid Build Coastguard Worker         for _ in 0..num_frames {
70*bb4ee6a4SAndroid Build Coastguard Worker             let compressed_size = u32::from_le_bytes(
71*bb4ee6a4SAndroid Build Coastguard Worker                 seek_table_entries
72*bb4ee6a4SAndroid Build Coastguard Worker                     .get(offset..offset + 4)
73*bb4ee6a4SAndroid Build Coastguard Worker                     .context("failed to parse seektable entry")?
74*bb4ee6a4SAndroid Build Coastguard Worker                     .try_into()?,
75*bb4ee6a4SAndroid Build Coastguard Worker             );
76*bb4ee6a4SAndroid Build Coastguard Worker             let decompressed_size = u32::from_le_bytes(
77*bb4ee6a4SAndroid Build Coastguard Worker                 seek_table_entries
78*bb4ee6a4SAndroid Build Coastguard Worker                     .get(offset + 4..offset + 8)
79*bb4ee6a4SAndroid Build Coastguard Worker                     .context("failed to parse seektable entry")?
80*bb4ee6a4SAndroid Build Coastguard Worker                     .try_into()?,
81*bb4ee6a4SAndroid Build Coastguard Worker             );
82*bb4ee6a4SAndroid Build Coastguard Worker             cumulative_decompressed_size += decompressed_size as u64;
83*bb4ee6a4SAndroid Build Coastguard Worker             cumulative_compressed_size += compressed_size as u64;
84*bb4ee6a4SAndroid Build Coastguard Worker             cumulative_decompressed_sizes.push(cumulative_decompressed_size);
85*bb4ee6a4SAndroid Build Coastguard Worker             cumulative_compressed_sizes.push(cumulative_compressed_size);
86*bb4ee6a4SAndroid Build Coastguard Worker             offset += 8 + (checksum_flag as usize * 4);
87*bb4ee6a4SAndroid Build Coastguard Worker         }
88*bb4ee6a4SAndroid Build Coastguard Worker         cumulative_decompressed_sizes.push(cumulative_decompressed_size);
89*bb4ee6a4SAndroid Build Coastguard Worker         cumulative_compressed_sizes.push(cumulative_compressed_size);
90*bb4ee6a4SAndroid Build Coastguard Worker 
91*bb4ee6a4SAndroid Build Coastguard Worker         Ok(ZstdSeekTable {
92*bb4ee6a4SAndroid Build Coastguard Worker             cumulative_decompressed_sizes,
93*bb4ee6a4SAndroid Build Coastguard Worker             cumulative_compressed_sizes,
94*bb4ee6a4SAndroid Build Coastguard Worker         })
95*bb4ee6a4SAndroid Build Coastguard Worker     }
96*bb4ee6a4SAndroid Build Coastguard Worker 
97*bb4ee6a4SAndroid Build Coastguard Worker     /// Returns the index of the frame that contains the given decompressed offset.
find_frame_index(&self, decompressed_offset: u64) -> Option<usize>98*bb4ee6a4SAndroid Build Coastguard Worker     pub fn find_frame_index(&self, decompressed_offset: u64) -> Option<usize> {
99*bb4ee6a4SAndroid Build Coastguard Worker         if self.cumulative_decompressed_sizes.is_empty()
100*bb4ee6a4SAndroid Build Coastguard Worker             || decompressed_offset >= *self.cumulative_decompressed_sizes.last().unwrap()
101*bb4ee6a4SAndroid Build Coastguard Worker         {
102*bb4ee6a4SAndroid Build Coastguard Worker             return None;
103*bb4ee6a4SAndroid Build Coastguard Worker         }
104*bb4ee6a4SAndroid Build Coastguard Worker         self.cumulative_decompressed_sizes
105*bb4ee6a4SAndroid Build Coastguard Worker             .partition_point(|&size| size <= decompressed_offset)
106*bb4ee6a4SAndroid Build Coastguard Worker             .checked_sub(1)
107*bb4ee6a4SAndroid Build Coastguard Worker     }
108*bb4ee6a4SAndroid Build Coastguard Worker }
109*bb4ee6a4SAndroid Build Coastguard Worker 
110*bb4ee6a4SAndroid Build Coastguard Worker #[derive(Debug)]
111*bb4ee6a4SAndroid Build Coastguard Worker pub struct ZstdDisk {
112*bb4ee6a4SAndroid Build Coastguard Worker     file: File,
113*bb4ee6a4SAndroid Build Coastguard Worker     seek_table: ZstdSeekTable,
114*bb4ee6a4SAndroid Build Coastguard Worker }
115*bb4ee6a4SAndroid Build Coastguard Worker 
116*bb4ee6a4SAndroid Build Coastguard Worker impl ZstdDisk {
from_file(mut file: File) -> anyhow::Result<ZstdDisk>117*bb4ee6a4SAndroid Build Coastguard Worker     pub fn from_file(mut file: File) -> anyhow::Result<ZstdDisk> {
118*bb4ee6a4SAndroid Build Coastguard Worker         // Verify file is large enough to contain a seek table (17 bytes)
119*bb4ee6a4SAndroid Build Coastguard Worker         if file.metadata()?.len() < 17 {
120*bb4ee6a4SAndroid Build Coastguard Worker             return Err(anyhow::anyhow!("File too small to contain zstd seek table"));
121*bb4ee6a4SAndroid Build Coastguard Worker         }
122*bb4ee6a4SAndroid Build Coastguard Worker 
123*bb4ee6a4SAndroid Build Coastguard Worker         // Read last 9 bytes as seek table footer
124*bb4ee6a4SAndroid Build Coastguard Worker         let mut seektable_footer = [0u8; 9];
125*bb4ee6a4SAndroid Build Coastguard Worker         file.seek(std::io::SeekFrom::End(-9))?;
126*bb4ee6a4SAndroid Build Coastguard Worker         file.read_exact(&mut seektable_footer)?;
127*bb4ee6a4SAndroid Build Coastguard Worker 
128*bb4ee6a4SAndroid Build Coastguard Worker         // Verify last 4 bytes of footer is seek table magic
129*bb4ee6a4SAndroid Build Coastguard Worker         if u32::from_le_bytes(seektable_footer[5..9].try_into()?) != ZSTD_SEEK_TABLE_MAGIC {
130*bb4ee6a4SAndroid Build Coastguard Worker             return Err(anyhow::anyhow!("Invalid zstd seek table magic"));
131*bb4ee6a4SAndroid Build Coastguard Worker         }
132*bb4ee6a4SAndroid Build Coastguard Worker 
133*bb4ee6a4SAndroid Build Coastguard Worker         // Get number of frame from seek table
134*bb4ee6a4SAndroid Build Coastguard Worker         let num_frames = u32::from_le_bytes(seektable_footer[0..4].try_into()?);
135*bb4ee6a4SAndroid Build Coastguard Worker 
136*bb4ee6a4SAndroid Build Coastguard Worker         // Read flags from seek table descriptor
137*bb4ee6a4SAndroid Build Coastguard Worker         let checksum_flag = (seektable_footer[4] >> 7) & 1 != 0;
138*bb4ee6a4SAndroid Build Coastguard Worker         if (seektable_footer[4] & 0x7C) != 0 {
139*bb4ee6a4SAndroid Build Coastguard Worker             bail!(
140*bb4ee6a4SAndroid Build Coastguard Worker                 "This zstd seekable decoder cannot parse seek table with non-zero reserved flags"
141*bb4ee6a4SAndroid Build Coastguard Worker             );
142*bb4ee6a4SAndroid Build Coastguard Worker         }
143*bb4ee6a4SAndroid Build Coastguard Worker 
144*bb4ee6a4SAndroid Build Coastguard Worker         let seek_table_entries_size = num_frames * (8 + (checksum_flag as u32 * 4));
145*bb4ee6a4SAndroid Build Coastguard Worker 
146*bb4ee6a4SAndroid Build Coastguard Worker         // Seek to the beginning of the seek table
147*bb4ee6a4SAndroid Build Coastguard Worker         file.seek(std::io::SeekFrom::End(
148*bb4ee6a4SAndroid Build Coastguard Worker             -(9 + seek_table_entries_size as i64),
149*bb4ee6a4SAndroid Build Coastguard Worker         ))?;
150*bb4ee6a4SAndroid Build Coastguard Worker 
151*bb4ee6a4SAndroid Build Coastguard Worker         // Return new ZstdDisk
152*bb4ee6a4SAndroid Build Coastguard Worker         let mut seek_table_entries: Vec<u8> = vec![0u8; seek_table_entries_size as usize];
153*bb4ee6a4SAndroid Build Coastguard Worker         file.read_exact(&mut seek_table_entries)?;
154*bb4ee6a4SAndroid Build Coastguard Worker 
155*bb4ee6a4SAndroid Build Coastguard Worker         let seek_table =
156*bb4ee6a4SAndroid Build Coastguard Worker             ZstdSeekTable::from_footer(&seek_table_entries, num_frames, checksum_flag)?;
157*bb4ee6a4SAndroid Build Coastguard Worker 
158*bb4ee6a4SAndroid Build Coastguard Worker         Ok(ZstdDisk { file, seek_table })
159*bb4ee6a4SAndroid Build Coastguard Worker     }
160*bb4ee6a4SAndroid Build Coastguard Worker }
161*bb4ee6a4SAndroid Build Coastguard Worker 
162*bb4ee6a4SAndroid Build Coastguard Worker impl DiskGetLen for ZstdDisk {
get_len(&self) -> std::io::Result<u64>163*bb4ee6a4SAndroid Build Coastguard Worker     fn get_len(&self) -> std::io::Result<u64> {
164*bb4ee6a4SAndroid Build Coastguard Worker         self.seek_table
165*bb4ee6a4SAndroid Build Coastguard Worker             .cumulative_decompressed_sizes
166*bb4ee6a4SAndroid Build Coastguard Worker             .last()
167*bb4ee6a4SAndroid Build Coastguard Worker             .copied()
168*bb4ee6a4SAndroid Build Coastguard Worker             .ok_or(io::ErrorKind::InvalidData.into())
169*bb4ee6a4SAndroid Build Coastguard Worker     }
170*bb4ee6a4SAndroid Build Coastguard Worker }
171*bb4ee6a4SAndroid Build Coastguard Worker 
172*bb4ee6a4SAndroid Build Coastguard Worker impl FileSetLen for ZstdDisk {
set_len(&self, _len: u64) -> std::io::Result<()>173*bb4ee6a4SAndroid Build Coastguard Worker     fn set_len(&self, _len: u64) -> std::io::Result<()> {
174*bb4ee6a4SAndroid Build Coastguard Worker         Err(io::Error::new(
175*bb4ee6a4SAndroid Build Coastguard Worker             io::ErrorKind::PermissionDenied,
176*bb4ee6a4SAndroid Build Coastguard Worker             "unsupported operation",
177*bb4ee6a4SAndroid Build Coastguard Worker         ))
178*bb4ee6a4SAndroid Build Coastguard Worker     }
179*bb4ee6a4SAndroid Build Coastguard Worker }
180*bb4ee6a4SAndroid Build Coastguard Worker 
181*bb4ee6a4SAndroid Build Coastguard Worker impl AsRawDescriptor for ZstdDisk {
as_raw_descriptor(&self) -> RawDescriptor182*bb4ee6a4SAndroid Build Coastguard Worker     fn as_raw_descriptor(&self) -> RawDescriptor {
183*bb4ee6a4SAndroid Build Coastguard Worker         self.file.as_raw_descriptor()
184*bb4ee6a4SAndroid Build Coastguard Worker     }
185*bb4ee6a4SAndroid Build Coastguard Worker }
186*bb4ee6a4SAndroid Build Coastguard Worker 
187*bb4ee6a4SAndroid Build Coastguard Worker struct CompressedReadInstruction {
188*bb4ee6a4SAndroid Build Coastguard Worker     frame_index: usize,
189*bb4ee6a4SAndroid Build Coastguard Worker     read_offset: u64,
190*bb4ee6a4SAndroid Build Coastguard Worker     read_size: u64,
191*bb4ee6a4SAndroid Build Coastguard Worker }
192*bb4ee6a4SAndroid Build Coastguard Worker 
compresed_frame_read_instruction( seek_table: &ZstdSeekTable, offset: u64, ) -> anyhow::Result<CompressedReadInstruction>193*bb4ee6a4SAndroid Build Coastguard Worker fn compresed_frame_read_instruction(
194*bb4ee6a4SAndroid Build Coastguard Worker     seek_table: &ZstdSeekTable,
195*bb4ee6a4SAndroid Build Coastguard Worker     offset: u64,
196*bb4ee6a4SAndroid Build Coastguard Worker ) -> anyhow::Result<CompressedReadInstruction> {
197*bb4ee6a4SAndroid Build Coastguard Worker     let frame_index = seek_table
198*bb4ee6a4SAndroid Build Coastguard Worker         .find_frame_index(offset)
199*bb4ee6a4SAndroid Build Coastguard Worker         .with_context(|| format!("no frame for offset {}", offset))?;
200*bb4ee6a4SAndroid Build Coastguard Worker     let compressed_offset = seek_table.cumulative_compressed_sizes[frame_index];
201*bb4ee6a4SAndroid Build Coastguard Worker     let next_compressed_offset = seek_table
202*bb4ee6a4SAndroid Build Coastguard Worker         .cumulative_compressed_sizes
203*bb4ee6a4SAndroid Build Coastguard Worker         .get(frame_index + 1)
204*bb4ee6a4SAndroid Build Coastguard Worker         .context("Offset out of range (next_compressed_offset overflow)")?;
205*bb4ee6a4SAndroid Build Coastguard Worker     let compressed_size = next_compressed_offset - compressed_offset;
206*bb4ee6a4SAndroid Build Coastguard Worker     Ok(CompressedReadInstruction {
207*bb4ee6a4SAndroid Build Coastguard Worker         frame_index,
208*bb4ee6a4SAndroid Build Coastguard Worker         read_offset: compressed_offset,
209*bb4ee6a4SAndroid Build Coastguard Worker         read_size: compressed_size,
210*bb4ee6a4SAndroid Build Coastguard Worker     })
211*bb4ee6a4SAndroid Build Coastguard Worker }
212*bb4ee6a4SAndroid Build Coastguard Worker 
213*bb4ee6a4SAndroid Build Coastguard Worker impl FileReadWriteAtVolatile for ZstdDisk {
read_at_volatile(&self, slice: VolatileSlice, offset: u64) -> io::Result<usize>214*bb4ee6a4SAndroid Build Coastguard Worker     fn read_at_volatile(&self, slice: VolatileSlice, offset: u64) -> io::Result<usize> {
215*bb4ee6a4SAndroid Build Coastguard Worker         let read_instruction = compresed_frame_read_instruction(&self.seek_table, offset)
216*bb4ee6a4SAndroid Build Coastguard Worker             .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
217*bb4ee6a4SAndroid Build Coastguard Worker 
218*bb4ee6a4SAndroid Build Coastguard Worker         let mut compressed_data = vec![0u8; read_instruction.read_size as usize];
219*bb4ee6a4SAndroid Build Coastguard Worker 
220*bb4ee6a4SAndroid Build Coastguard Worker         let compressed_frame_slice = VolatileSlice::new(compressed_data.as_mut_slice());
221*bb4ee6a4SAndroid Build Coastguard Worker 
222*bb4ee6a4SAndroid Build Coastguard Worker         self.file
223*bb4ee6a4SAndroid Build Coastguard Worker             .read_at_volatile(compressed_frame_slice, read_instruction.read_offset)
224*bb4ee6a4SAndroid Build Coastguard Worker             .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
225*bb4ee6a4SAndroid Build Coastguard Worker 
226*bb4ee6a4SAndroid Build Coastguard Worker         let mut decompressor: zstd::bulk::Decompressor<'_> = zstd::bulk::Decompressor::new()?;
227*bb4ee6a4SAndroid Build Coastguard Worker         let mut decompressed_data = Vec::with_capacity(ZSTD_DEFAULT_FRAME_SIZE);
228*bb4ee6a4SAndroid Build Coastguard Worker         let decoded_size =
229*bb4ee6a4SAndroid Build Coastguard Worker             decompressor.decompress_to_buffer(&compressed_data, &mut decompressed_data)?;
230*bb4ee6a4SAndroid Build Coastguard Worker 
231*bb4ee6a4SAndroid Build Coastguard Worker         let decompressed_offset_in_frame =
232*bb4ee6a4SAndroid Build Coastguard Worker             offset - self.seek_table.cumulative_decompressed_sizes[read_instruction.frame_index];
233*bb4ee6a4SAndroid Build Coastguard Worker 
234*bb4ee6a4SAndroid Build Coastguard Worker         if decompressed_offset_in_frame >= decoded_size as u64 {
235*bb4ee6a4SAndroid Build Coastguard Worker             return Err(io::Error::new(
236*bb4ee6a4SAndroid Build Coastguard Worker                 io::ErrorKind::InvalidData,
237*bb4ee6a4SAndroid Build Coastguard Worker                 "BUG: Frame offset larger than decoded size",
238*bb4ee6a4SAndroid Build Coastguard Worker             ));
239*bb4ee6a4SAndroid Build Coastguard Worker         }
240*bb4ee6a4SAndroid Build Coastguard Worker 
241*bb4ee6a4SAndroid Build Coastguard Worker         let read_len = min(
242*bb4ee6a4SAndroid Build Coastguard Worker             slice.size() as u64,
243*bb4ee6a4SAndroid Build Coastguard Worker             (decoded_size as u64) - decompressed_offset_in_frame,
244*bb4ee6a4SAndroid Build Coastguard Worker         ) as usize;
245*bb4ee6a4SAndroid Build Coastguard Worker         let data_to_copy = &decompressed_data[decompressed_offset_in_frame as usize..][..read_len];
246*bb4ee6a4SAndroid Build Coastguard Worker         slice
247*bb4ee6a4SAndroid Build Coastguard Worker             .sub_slice(0, read_len)
248*bb4ee6a4SAndroid Build Coastguard Worker             .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?
249*bb4ee6a4SAndroid Build Coastguard Worker             .copy_from(data_to_copy);
250*bb4ee6a4SAndroid Build Coastguard Worker         Ok(data_to_copy.len())
251*bb4ee6a4SAndroid Build Coastguard Worker     }
252*bb4ee6a4SAndroid Build Coastguard Worker 
write_at_volatile(&self, _slice: VolatileSlice, _offset: u64) -> io::Result<usize>253*bb4ee6a4SAndroid Build Coastguard Worker     fn write_at_volatile(&self, _slice: VolatileSlice, _offset: u64) -> io::Result<usize> {
254*bb4ee6a4SAndroid Build Coastguard Worker         Err(io::Error::new(
255*bb4ee6a4SAndroid Build Coastguard Worker             io::ErrorKind::PermissionDenied,
256*bb4ee6a4SAndroid Build Coastguard Worker             "unsupported operation",
257*bb4ee6a4SAndroid Build Coastguard Worker         ))
258*bb4ee6a4SAndroid Build Coastguard Worker     }
259*bb4ee6a4SAndroid Build Coastguard Worker }
260*bb4ee6a4SAndroid Build Coastguard Worker 
261*bb4ee6a4SAndroid Build Coastguard Worker pub struct AsyncZstdDisk {
262*bb4ee6a4SAndroid Build Coastguard Worker     inner: IoSource<File>,
263*bb4ee6a4SAndroid Build Coastguard Worker     seek_table: ZstdSeekTable,
264*bb4ee6a4SAndroid Build Coastguard Worker }
265*bb4ee6a4SAndroid Build Coastguard Worker 
266*bb4ee6a4SAndroid Build Coastguard Worker impl ToAsyncDisk for ZstdDisk {
to_async_disk(self: Box<Self>, ex: &Executor) -> DiskResult<Box<dyn AsyncDisk>>267*bb4ee6a4SAndroid Build Coastguard Worker     fn to_async_disk(self: Box<Self>, ex: &Executor) -> DiskResult<Box<dyn AsyncDisk>> {
268*bb4ee6a4SAndroid Build Coastguard Worker         Ok(Box::new(AsyncZstdDisk {
269*bb4ee6a4SAndroid Build Coastguard Worker             inner: ex.async_from(self.file).map_err(DiskError::ToAsync)?,
270*bb4ee6a4SAndroid Build Coastguard Worker             seek_table: self.seek_table,
271*bb4ee6a4SAndroid Build Coastguard Worker         }))
272*bb4ee6a4SAndroid Build Coastguard Worker     }
273*bb4ee6a4SAndroid Build Coastguard Worker }
274*bb4ee6a4SAndroid Build Coastguard Worker 
275*bb4ee6a4SAndroid Build Coastguard Worker impl DiskGetLen for AsyncZstdDisk {
get_len(&self) -> io::Result<u64>276*bb4ee6a4SAndroid Build Coastguard Worker     fn get_len(&self) -> io::Result<u64> {
277*bb4ee6a4SAndroid Build Coastguard Worker         self.seek_table
278*bb4ee6a4SAndroid Build Coastguard Worker             .cumulative_decompressed_sizes
279*bb4ee6a4SAndroid Build Coastguard Worker             .last()
280*bb4ee6a4SAndroid Build Coastguard Worker             .copied()
281*bb4ee6a4SAndroid Build Coastguard Worker             .ok_or(io::ErrorKind::InvalidData.into())
282*bb4ee6a4SAndroid Build Coastguard Worker     }
283*bb4ee6a4SAndroid Build Coastguard Worker }
284*bb4ee6a4SAndroid Build Coastguard Worker 
285*bb4ee6a4SAndroid Build Coastguard Worker impl FileSetLen for AsyncZstdDisk {
set_len(&self, _len: u64) -> io::Result<()>286*bb4ee6a4SAndroid Build Coastguard Worker     fn set_len(&self, _len: u64) -> io::Result<()> {
287*bb4ee6a4SAndroid Build Coastguard Worker         Err(io::Error::new(
288*bb4ee6a4SAndroid Build Coastguard Worker             io::ErrorKind::PermissionDenied,
289*bb4ee6a4SAndroid Build Coastguard Worker             "unsupported operation",
290*bb4ee6a4SAndroid Build Coastguard Worker         ))
291*bb4ee6a4SAndroid Build Coastguard Worker     }
292*bb4ee6a4SAndroid Build Coastguard Worker }
293*bb4ee6a4SAndroid Build Coastguard Worker 
294*bb4ee6a4SAndroid Build Coastguard Worker impl FileAllocate for AsyncZstdDisk {
allocate(&self, _offset: u64, _length: u64) -> io::Result<()>295*bb4ee6a4SAndroid Build Coastguard Worker     fn allocate(&self, _offset: u64, _length: u64) -> io::Result<()> {
296*bb4ee6a4SAndroid Build Coastguard Worker         Err(io::Error::new(
297*bb4ee6a4SAndroid Build Coastguard Worker             io::ErrorKind::PermissionDenied,
298*bb4ee6a4SAndroid Build Coastguard Worker             "unsupported operation",
299*bb4ee6a4SAndroid Build Coastguard Worker         ))
300*bb4ee6a4SAndroid Build Coastguard Worker     }
301*bb4ee6a4SAndroid Build Coastguard Worker }
302*bb4ee6a4SAndroid Build Coastguard Worker 
303*bb4ee6a4SAndroid Build Coastguard Worker #[async_trait(?Send)]
304*bb4ee6a4SAndroid Build Coastguard Worker impl AsyncDisk for AsyncZstdDisk {
flush(&self) -> DiskResult<()>305*bb4ee6a4SAndroid Build Coastguard Worker     async fn flush(&self) -> DiskResult<()> {
306*bb4ee6a4SAndroid Build Coastguard Worker         // zstd is read-only, nothing to flush.
307*bb4ee6a4SAndroid Build Coastguard Worker         Ok(())
308*bb4ee6a4SAndroid Build Coastguard Worker     }
309*bb4ee6a4SAndroid Build Coastguard Worker 
fsync(&self) -> DiskResult<()>310*bb4ee6a4SAndroid Build Coastguard Worker     async fn fsync(&self) -> DiskResult<()> {
311*bb4ee6a4SAndroid Build Coastguard Worker         // Do nothing because it's read-only.
312*bb4ee6a4SAndroid Build Coastguard Worker         Ok(())
313*bb4ee6a4SAndroid Build Coastguard Worker     }
314*bb4ee6a4SAndroid Build Coastguard Worker 
fdatasync(&self) -> DiskResult<()>315*bb4ee6a4SAndroid Build Coastguard Worker     async fn fdatasync(&self) -> DiskResult<()> {
316*bb4ee6a4SAndroid Build Coastguard Worker         // Do nothing because it's read-only.
317*bb4ee6a4SAndroid Build Coastguard Worker         Ok(())
318*bb4ee6a4SAndroid Build Coastguard Worker     }
319*bb4ee6a4SAndroid Build Coastguard Worker 
320*bb4ee6a4SAndroid Build Coastguard Worker     /// Reads data from `file_offset` of decompressed disk image till the end of current
321*bb4ee6a4SAndroid Build Coastguard Worker     /// zstd frame and write them into memory `mem` at `mem_offsets`. This function should
322*bb4ee6a4SAndroid Build Coastguard Worker     /// function the same as running `preadv()` on decompressed zstd image and reading into
323*bb4ee6a4SAndroid Build Coastguard Worker     /// the array of `iovec`s specified with `mem` and `mem_offsets`.
read_to_mem<'a>( &'a self, file_offset: u64, mem: Arc<dyn BackingMemory + Send + Sync>, mem_offsets: cros_async::MemRegionIter<'a>, ) -> DiskResult<usize>324*bb4ee6a4SAndroid Build Coastguard Worker     async fn read_to_mem<'a>(
325*bb4ee6a4SAndroid Build Coastguard Worker         &'a self,
326*bb4ee6a4SAndroid Build Coastguard Worker         file_offset: u64,
327*bb4ee6a4SAndroid Build Coastguard Worker         mem: Arc<dyn BackingMemory + Send + Sync>,
328*bb4ee6a4SAndroid Build Coastguard Worker         mem_offsets: cros_async::MemRegionIter<'a>,
329*bb4ee6a4SAndroid Build Coastguard Worker     ) -> DiskResult<usize> {
330*bb4ee6a4SAndroid Build Coastguard Worker         let read_instruction = compresed_frame_read_instruction(&self.seek_table, file_offset)
331*bb4ee6a4SAndroid Build Coastguard Worker             .map_err(|e| DiskError::ReadingData(io::Error::new(io::ErrorKind::InvalidData, e)))?;
332*bb4ee6a4SAndroid Build Coastguard Worker 
333*bb4ee6a4SAndroid Build Coastguard Worker         let compressed_data = vec![0u8; read_instruction.read_size as usize];
334*bb4ee6a4SAndroid Build Coastguard Worker 
335*bb4ee6a4SAndroid Build Coastguard Worker         let (compressed_read_size, compressed_data) = self
336*bb4ee6a4SAndroid Build Coastguard Worker             .inner
337*bb4ee6a4SAndroid Build Coastguard Worker             .read_to_vec(Some(read_instruction.read_offset), compressed_data)
338*bb4ee6a4SAndroid Build Coastguard Worker             .await
339*bb4ee6a4SAndroid Build Coastguard Worker             .map_err(|e| DiskError::ReadingData(io::Error::new(ErrorKind::Other, e)))?;
340*bb4ee6a4SAndroid Build Coastguard Worker 
341*bb4ee6a4SAndroid Build Coastguard Worker         if compressed_read_size != read_instruction.read_size as usize {
342*bb4ee6a4SAndroid Build Coastguard Worker             return Err(DiskError::ReadingData(io::Error::new(
343*bb4ee6a4SAndroid Build Coastguard Worker                 ErrorKind::UnexpectedEof,
344*bb4ee6a4SAndroid Build Coastguard Worker                 "Read from compressed data result in wrong length",
345*bb4ee6a4SAndroid Build Coastguard Worker             )));
346*bb4ee6a4SAndroid Build Coastguard Worker         }
347*bb4ee6a4SAndroid Build Coastguard Worker 
348*bb4ee6a4SAndroid Build Coastguard Worker         let mut decompressor: zstd::bulk::Decompressor<'_> =
349*bb4ee6a4SAndroid Build Coastguard Worker             zstd::bulk::Decompressor::new().map_err(DiskError::ReadingData)?;
350*bb4ee6a4SAndroid Build Coastguard Worker         let mut decompressed_data = Vec::with_capacity(ZSTD_DEFAULT_FRAME_SIZE);
351*bb4ee6a4SAndroid Build Coastguard Worker         let decoded_size = decompressor
352*bb4ee6a4SAndroid Build Coastguard Worker             .decompress_to_buffer(&compressed_data, &mut decompressed_data)
353*bb4ee6a4SAndroid Build Coastguard Worker             .map_err(DiskError::ReadingData)?;
354*bb4ee6a4SAndroid Build Coastguard Worker 
355*bb4ee6a4SAndroid Build Coastguard Worker         let decompressed_offset_in_frame = file_offset
356*bb4ee6a4SAndroid Build Coastguard Worker             - self.seek_table.cumulative_decompressed_sizes[read_instruction.frame_index];
357*bb4ee6a4SAndroid Build Coastguard Worker 
358*bb4ee6a4SAndroid Build Coastguard Worker         if decompressed_offset_in_frame as usize > decoded_size {
359*bb4ee6a4SAndroid Build Coastguard Worker             return Err(DiskError::ReadingData(io::Error::new(
360*bb4ee6a4SAndroid Build Coastguard Worker                 ErrorKind::InvalidData,
361*bb4ee6a4SAndroid Build Coastguard Worker                 "BUG: Frame offset larger than decoded size",
362*bb4ee6a4SAndroid Build Coastguard Worker             )));
363*bb4ee6a4SAndroid Build Coastguard Worker         }
364*bb4ee6a4SAndroid Build Coastguard Worker 
365*bb4ee6a4SAndroid Build Coastguard Worker         // Copy the decompressed data to the provided memory regions.
366*bb4ee6a4SAndroid Build Coastguard Worker         let mut total_copied = 0;
367*bb4ee6a4SAndroid Build Coastguard Worker         for mem_region in mem_offsets {
368*bb4ee6a4SAndroid Build Coastguard Worker             let src_slice =
369*bb4ee6a4SAndroid Build Coastguard Worker                 &decompressed_data[decompressed_offset_in_frame as usize + total_copied..];
370*bb4ee6a4SAndroid Build Coastguard Worker             let dst_slice = mem
371*bb4ee6a4SAndroid Build Coastguard Worker                 .get_volatile_slice(mem_region)
372*bb4ee6a4SAndroid Build Coastguard Worker                 .map_err(DiskError::GuestMemory)?;
373*bb4ee6a4SAndroid Build Coastguard Worker 
374*bb4ee6a4SAndroid Build Coastguard Worker             let to_copy = min(src_slice.len(), dst_slice.size());
375*bb4ee6a4SAndroid Build Coastguard Worker 
376*bb4ee6a4SAndroid Build Coastguard Worker             if to_copy > 0 {
377*bb4ee6a4SAndroid Build Coastguard Worker                 dst_slice
378*bb4ee6a4SAndroid Build Coastguard Worker                     .sub_slice(0, to_copy)
379*bb4ee6a4SAndroid Build Coastguard Worker                     .map_err(|e| DiskError::ReadingData(io::Error::new(ErrorKind::Other, e)))?
380*bb4ee6a4SAndroid Build Coastguard Worker                     .copy_from(&src_slice[..to_copy]);
381*bb4ee6a4SAndroid Build Coastguard Worker 
382*bb4ee6a4SAndroid Build Coastguard Worker                 total_copied += to_copy;
383*bb4ee6a4SAndroid Build Coastguard Worker 
384*bb4ee6a4SAndroid Build Coastguard Worker                 // if fully copied destination buffers, break the loop.
385*bb4ee6a4SAndroid Build Coastguard Worker                 if total_copied == dst_slice.size() {
386*bb4ee6a4SAndroid Build Coastguard Worker                     break;
387*bb4ee6a4SAndroid Build Coastguard Worker                 }
388*bb4ee6a4SAndroid Build Coastguard Worker             }
389*bb4ee6a4SAndroid Build Coastguard Worker         }
390*bb4ee6a4SAndroid Build Coastguard Worker 
391*bb4ee6a4SAndroid Build Coastguard Worker         Ok(total_copied)
392*bb4ee6a4SAndroid Build Coastguard Worker     }
393*bb4ee6a4SAndroid Build Coastguard Worker 
write_from_mem<'a>( &'a self, _file_offset: u64, _mem: Arc<dyn BackingMemory + Send + Sync>, _mem_offsets: cros_async::MemRegionIter<'a>, ) -> DiskResult<usize>394*bb4ee6a4SAndroid Build Coastguard Worker     async fn write_from_mem<'a>(
395*bb4ee6a4SAndroid Build Coastguard Worker         &'a self,
396*bb4ee6a4SAndroid Build Coastguard Worker         _file_offset: u64,
397*bb4ee6a4SAndroid Build Coastguard Worker         _mem: Arc<dyn BackingMemory + Send + Sync>,
398*bb4ee6a4SAndroid Build Coastguard Worker         _mem_offsets: cros_async::MemRegionIter<'a>,
399*bb4ee6a4SAndroid Build Coastguard Worker     ) -> DiskResult<usize> {
400*bb4ee6a4SAndroid Build Coastguard Worker         Err(DiskError::UnsupportedOperation)
401*bb4ee6a4SAndroid Build Coastguard Worker     }
402*bb4ee6a4SAndroid Build Coastguard Worker 
punch_hole(&self, _file_offset: u64, _length: u64) -> DiskResult<()>403*bb4ee6a4SAndroid Build Coastguard Worker     async fn punch_hole(&self, _file_offset: u64, _length: u64) -> DiskResult<()> {
404*bb4ee6a4SAndroid Build Coastguard Worker         Err(DiskError::UnsupportedOperation)
405*bb4ee6a4SAndroid Build Coastguard Worker     }
406*bb4ee6a4SAndroid Build Coastguard Worker 
write_zeroes_at(&self, _file_offset: u64, _length: u64) -> DiskResult<()>407*bb4ee6a4SAndroid Build Coastguard Worker     async fn write_zeroes_at(&self, _file_offset: u64, _length: u64) -> DiskResult<()> {
408*bb4ee6a4SAndroid Build Coastguard Worker         Err(DiskError::UnsupportedOperation)
409*bb4ee6a4SAndroid Build Coastguard Worker     }
410*bb4ee6a4SAndroid Build Coastguard Worker }
411*bb4ee6a4SAndroid Build Coastguard Worker 
412*bb4ee6a4SAndroid Build Coastguard Worker impl DiskFile for ZstdDisk {}
413*bb4ee6a4SAndroid Build Coastguard Worker 
414*bb4ee6a4SAndroid Build Coastguard Worker #[cfg(test)]
415*bb4ee6a4SAndroid Build Coastguard Worker mod tests {
416*bb4ee6a4SAndroid Build Coastguard Worker     use super::*;
417*bb4ee6a4SAndroid Build Coastguard Worker 
418*bb4ee6a4SAndroid Build Coastguard Worker     #[test]
test_find_frame_index_empty()419*bb4ee6a4SAndroid Build Coastguard Worker     fn test_find_frame_index_empty() {
420*bb4ee6a4SAndroid Build Coastguard Worker         let seek_table = ZstdSeekTable {
421*bb4ee6a4SAndroid Build Coastguard Worker             cumulative_decompressed_sizes: vec![0],
422*bb4ee6a4SAndroid Build Coastguard Worker             cumulative_compressed_sizes: vec![0],
423*bb4ee6a4SAndroid Build Coastguard Worker         };
424*bb4ee6a4SAndroid Build Coastguard Worker         assert_eq!(seek_table.find_frame_index(0), None);
425*bb4ee6a4SAndroid Build Coastguard Worker         assert_eq!(seek_table.find_frame_index(5), None);
426*bb4ee6a4SAndroid Build Coastguard Worker     }
427*bb4ee6a4SAndroid Build Coastguard Worker 
428*bb4ee6a4SAndroid Build Coastguard Worker     #[test]
test_find_frame_index_single_frame()429*bb4ee6a4SAndroid Build Coastguard Worker     fn test_find_frame_index_single_frame() {
430*bb4ee6a4SAndroid Build Coastguard Worker         let seek_table = ZstdSeekTable {
431*bb4ee6a4SAndroid Build Coastguard Worker             cumulative_decompressed_sizes: vec![0, 100],
432*bb4ee6a4SAndroid Build Coastguard Worker             cumulative_compressed_sizes: vec![0, 50],
433*bb4ee6a4SAndroid Build Coastguard Worker         };
434*bb4ee6a4SAndroid Build Coastguard Worker         assert_eq!(seek_table.find_frame_index(0), Some(0));
435*bb4ee6a4SAndroid Build Coastguard Worker         assert_eq!(seek_table.find_frame_index(50), Some(0));
436*bb4ee6a4SAndroid Build Coastguard Worker         assert_eq!(seek_table.find_frame_index(99), Some(0));
437*bb4ee6a4SAndroid Build Coastguard Worker         assert_eq!(seek_table.find_frame_index(100), None);
438*bb4ee6a4SAndroid Build Coastguard Worker     }
439*bb4ee6a4SAndroid Build Coastguard Worker 
440*bb4ee6a4SAndroid Build Coastguard Worker     #[test]
test_find_frame_index_multiple_frames()441*bb4ee6a4SAndroid Build Coastguard Worker     fn test_find_frame_index_multiple_frames() {
442*bb4ee6a4SAndroid Build Coastguard Worker         let seek_table = ZstdSeekTable {
443*bb4ee6a4SAndroid Build Coastguard Worker             cumulative_decompressed_sizes: vec![0, 100, 300, 500],
444*bb4ee6a4SAndroid Build Coastguard Worker             cumulative_compressed_sizes: vec![0, 50, 120, 200],
445*bb4ee6a4SAndroid Build Coastguard Worker         };
446*bb4ee6a4SAndroid Build Coastguard Worker         assert_eq!(seek_table.find_frame_index(0), Some(0));
447*bb4ee6a4SAndroid Build Coastguard Worker         assert_eq!(seek_table.find_frame_index(99), Some(0));
448*bb4ee6a4SAndroid Build Coastguard Worker         assert_eq!(seek_table.find_frame_index(100), Some(1));
449*bb4ee6a4SAndroid Build Coastguard Worker         assert_eq!(seek_table.find_frame_index(299), Some(1));
450*bb4ee6a4SAndroid Build Coastguard Worker         assert_eq!(seek_table.find_frame_index(300), Some(2));
451*bb4ee6a4SAndroid Build Coastguard Worker         assert_eq!(seek_table.find_frame_index(499), Some(2));
452*bb4ee6a4SAndroid Build Coastguard Worker         assert_eq!(seek_table.find_frame_index(500), None);
453*bb4ee6a4SAndroid Build Coastguard Worker         assert_eq!(seek_table.find_frame_index(1000), None);
454*bb4ee6a4SAndroid Build Coastguard Worker     }
455*bb4ee6a4SAndroid Build Coastguard Worker 
456*bb4ee6a4SAndroid Build Coastguard Worker     #[test]
test_find_frame_index_with_skippable_frames()457*bb4ee6a4SAndroid Build Coastguard Worker     fn test_find_frame_index_with_skippable_frames() {
458*bb4ee6a4SAndroid Build Coastguard Worker         let seek_table = ZstdSeekTable {
459*bb4ee6a4SAndroid Build Coastguard Worker             cumulative_decompressed_sizes: vec![0, 100, 100, 100, 300],
460*bb4ee6a4SAndroid Build Coastguard Worker             cumulative_compressed_sizes: vec![0, 50, 60, 70, 150],
461*bb4ee6a4SAndroid Build Coastguard Worker         };
462*bb4ee6a4SAndroid Build Coastguard Worker         assert_eq!(seek_table.find_frame_index(0), Some(0));
463*bb4ee6a4SAndroid Build Coastguard Worker         assert_eq!(seek_table.find_frame_index(99), Some(0));
464*bb4ee6a4SAndroid Build Coastguard Worker         // Correctly skips the skippable frames.
465*bb4ee6a4SAndroid Build Coastguard Worker         assert_eq!(seek_table.find_frame_index(100), Some(3));
466*bb4ee6a4SAndroid Build Coastguard Worker         assert_eq!(seek_table.find_frame_index(299), Some(3));
467*bb4ee6a4SAndroid Build Coastguard Worker         assert_eq!(seek_table.find_frame_index(300), None);
468*bb4ee6a4SAndroid Build Coastguard Worker     }
469*bb4ee6a4SAndroid Build Coastguard Worker 
470*bb4ee6a4SAndroid Build Coastguard Worker     #[test]
test_find_frame_index_with_last_skippable_frame()471*bb4ee6a4SAndroid Build Coastguard Worker     fn test_find_frame_index_with_last_skippable_frame() {
472*bb4ee6a4SAndroid Build Coastguard Worker         let seek_table = ZstdSeekTable {
473*bb4ee6a4SAndroid Build Coastguard Worker             cumulative_decompressed_sizes: vec![0, 20, 40, 40, 60, 60, 80, 80],
474*bb4ee6a4SAndroid Build Coastguard Worker             cumulative_compressed_sizes: vec![0, 10, 20, 30, 40, 50, 60, 70],
475*bb4ee6a4SAndroid Build Coastguard Worker         };
476*bb4ee6a4SAndroid Build Coastguard Worker         assert_eq!(seek_table.find_frame_index(0), Some(0));
477*bb4ee6a4SAndroid Build Coastguard Worker         assert_eq!(seek_table.find_frame_index(20), Some(1));
478*bb4ee6a4SAndroid Build Coastguard Worker         assert_eq!(seek_table.find_frame_index(21), Some(1));
479*bb4ee6a4SAndroid Build Coastguard Worker         assert_eq!(seek_table.find_frame_index(79), Some(5));
480*bb4ee6a4SAndroid Build Coastguard Worker         assert_eq!(seek_table.find_frame_index(80), None);
481*bb4ee6a4SAndroid Build Coastguard Worker         assert_eq!(seek_table.find_frame_index(300), None);
482*bb4ee6a4SAndroid Build Coastguard Worker     }
483*bb4ee6a4SAndroid Build Coastguard Worker }
484