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