mirror of
https://github.com/chenxiaolong/avbroot.git
synced 2026-07-03 14:05:11 +02:00
Merge pull request #228 from chenxiaolong/parallel_compress
Split new partition images into chunks and compress in parallel
This commit is contained in:
+2
-1
@@ -528,7 +528,8 @@ impl PrepatchedImagePatcher {
|
||||
const MAX_LEVEL: u8 = 2;
|
||||
|
||||
// We compile without Unicode support so we have to use [0-9] instead of \d.
|
||||
const VERSION_REGEX: &'static str = r"Linux version ([0-9]+\.[0-9]+).[0-9]+-(android[0-9]+)-([0-9]+)-";
|
||||
const VERSION_REGEX: &'static str =
|
||||
r"Linux version ([0-9]+\.[0-9]+).[0-9]+-(android[0-9]+)-([0-9]+)-";
|
||||
|
||||
pub fn new(
|
||||
prepatched: &Path,
|
||||
|
||||
+84
-64
@@ -9,7 +9,7 @@ use std::{
|
||||
ffi::{OsStr, OsString},
|
||||
fmt::Display,
|
||||
fs::{self, File},
|
||||
io::{BufReader, BufWriter, Cursor, Read, Seek, SeekFrom, Write},
|
||||
io::{BufReader, BufWriter, Read, Seek, SeekFrom, Write},
|
||||
path::{Path, PathBuf},
|
||||
sync::{atomic::AtomicBool, Mutex},
|
||||
time::Instant,
|
||||
@@ -37,14 +37,14 @@ use crate::{
|
||||
bootimage::BootImage,
|
||||
ota::{self, SigningWriter, ZipEntry},
|
||||
padding,
|
||||
payload::{self, CompressedPartitionWriter, PayloadHeader, PayloadWriter},
|
||||
payload::{self, PayloadHeader, PayloadWriter},
|
||||
},
|
||||
protobuf::{
|
||||
build::tools::releasetools::OtaMetadata, chromeos_update_engine::DeltaArchiveManifest,
|
||||
},
|
||||
stream::{
|
||||
self, CountingWriter, FromReader, HolePunchingWriter, PSeekFile, ReadSeek, ReadSeekReopen,
|
||||
Reopen, SectionReader, ToWriter,
|
||||
self, CountingWriter, FromReader, HolePunchingWriter, PSeekFile, ReadSeekReopen, Reopen,
|
||||
SectionReader, ToWriter,
|
||||
},
|
||||
util,
|
||||
};
|
||||
@@ -150,17 +150,18 @@ pub fn get_required_images(
|
||||
Ok(images)
|
||||
}
|
||||
|
||||
/// Open all input streams listed in `required_images`. If an image has a path
|
||||
/// in `external_images`, the real file on the filesystem is opened. Otherwise,
|
||||
/// the image is extracted from the payload.
|
||||
fn open_input_streams(
|
||||
/// Open all input files listed in `required_images`. If an image has a path
|
||||
/// in `external_images`, that file is opened. Otherwise, the image is extracted
|
||||
/// from the payload into a temporary file (that is unnamed if supported by the
|
||||
/// operating system).
|
||||
fn open_input_files(
|
||||
payload: &(dyn ReadSeekReopen + Sync),
|
||||
required_images: &HashMap<String, String>,
|
||||
external_images: &HashMap<String, PathBuf>,
|
||||
header: &PayloadHeader,
|
||||
cancel_signal: &AtomicBool,
|
||||
) -> Result<HashMap<String, Box<dyn ReadSeek + Send>>> {
|
||||
let mut input_streams = HashMap::<String, Box<dyn ReadSeek + Send>>::new();
|
||||
) -> Result<HashMap<String, PSeekFile>> {
|
||||
let mut input_files = HashMap::<String, PSeekFile>::new();
|
||||
|
||||
// We always include replacement images that the user specifies, even if
|
||||
// they don't need to be patched.
|
||||
@@ -174,18 +175,23 @@ fn open_input_streams(
|
||||
status!("Opening external image: {name}: {path:?}");
|
||||
|
||||
let file = File::open(path)
|
||||
.map(PSeekFile::new)
|
||||
.with_context(|| format!("Failed to open external image: {path:?}"))?;
|
||||
input_streams.insert(name.clone(), Box::new(file));
|
||||
input_files.insert(name.clone(), file);
|
||||
} else {
|
||||
status!("Extracting from original payload: {name}");
|
||||
|
||||
let stream = payload::extract_image_to_memory(payload, header, name, cancel_signal)
|
||||
let file = tempfile::tempfile()
|
||||
.map(PSeekFile::new)
|
||||
.with_context(|| format!("Failed to create temp file for: {name}"))?;
|
||||
|
||||
payload::extract_image(payload, &file, header, name, cancel_signal)
|
||||
.with_context(|| format!("Failed to extract from original payload: {name}"))?;
|
||||
input_streams.insert(name.clone(), Box::new(stream));
|
||||
input_files.insert(name.clone(), file);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(input_streams)
|
||||
Ok(input_files)
|
||||
}
|
||||
|
||||
/// Patch the boot images listed in `required_images`. An [`OtaCertPatcher`] is
|
||||
@@ -195,7 +201,7 @@ fn open_input_streams(
|
||||
/// be re-signed with `key_avb`.
|
||||
fn patch_boot_images(
|
||||
required_images: &HashMap<String, String>,
|
||||
input_streams: &mut HashMap<String, Box<dyn ReadSeek + Send>>,
|
||||
input_files: &mut HashMap<String, PSeekFile>,
|
||||
root_patcher: Option<Box<dyn BootImagePatcher + Send>>,
|
||||
key_avb: &RsaPrivateKey,
|
||||
cert_ota: &Certificate,
|
||||
@@ -219,29 +225,31 @@ fn patch_boot_images(
|
||||
joined(sorted(boot_patchers.keys()))
|
||||
);
|
||||
|
||||
// Temporarily take the streams out of input_streams so we can easily
|
||||
// run the patchers in parallel.
|
||||
// Temporarily take the files out of input_files so we can easily run the
|
||||
// patchers in parallel.
|
||||
let patchers_list = boot_patchers
|
||||
.into_iter()
|
||||
.map(|(n, p)| (n, p, input_streams.remove(n).unwrap()))
|
||||
.map(|(n, p)| (n, p, input_files.remove(n).unwrap()))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Patch the boot images. The original readers are dropped.
|
||||
let patched = patchers_list
|
||||
.into_par_iter()
|
||||
.map(|(n, p, s)| -> Result<(&str, Cursor<Vec<u8>>)> {
|
||||
let mut writer = Cursor::new(Vec::new());
|
||||
.map(|(n, p, f)| -> Result<(&str, PSeekFile)> {
|
||||
let mut new_file = tempfile::tempfile()
|
||||
.map(PSeekFile::new)
|
||||
.with_context(|| format!("Failed to create temp file for: {n}"))?;
|
||||
|
||||
boot::patch_boot(s, &mut writer, key_avb, &p, cancel_signal)
|
||||
boot::patch_boot(f, &mut new_file, key_avb, &p, cancel_signal)
|
||||
.with_context(|| format!("Failed to patch boot image: {n}"))?;
|
||||
|
||||
Ok((n, writer))
|
||||
Ok((n, new_file))
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
|
||||
// Put the patched images back into input_streams.
|
||||
for (name, stream) in patched {
|
||||
input_streams.insert(name.to_owned(), Box::new(stream));
|
||||
// Put the patched images back into input_files.
|
||||
for (name, file) in patched {
|
||||
input_files.insert(name.to_owned(), file);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -251,7 +259,7 @@ fn patch_boot_images(
|
||||
/// then an error is returned because the vbmeta patching logic only ever writes
|
||||
/// root vbmeta images.
|
||||
fn load_vbmeta_images(
|
||||
images: &mut HashMap<String, Box<dyn ReadSeek + Send>>,
|
||||
images: &mut HashMap<String, PSeekFile>,
|
||||
vbmeta_images: &HashSet<String>,
|
||||
) -> Result<HashMap<String, Header>> {
|
||||
let mut result = HashMap::new();
|
||||
@@ -325,7 +333,7 @@ fn ensure_partitions_protected(
|
||||
/// determine the order to patch the vbmeta images so that it can be done in a
|
||||
/// single pass.
|
||||
fn get_vbmeta_patch_order(
|
||||
images: &mut HashMap<String, Box<dyn ReadSeek + Send>>,
|
||||
images: &mut HashMap<String, PSeekFile>,
|
||||
vbmeta_headers: &HashMap<String, Header>,
|
||||
) -> Result<Vec<(String, HashSet<String>)>> {
|
||||
let mut dep_graph = HashMap::<&str, HashSet<String>>::new();
|
||||
@@ -533,7 +541,7 @@ fn update_metadata_descriptors(parent_header: &mut Header, child_header: &Header
|
||||
/// replaced with a new in-memory reader containing the new image. Otherwise,
|
||||
/// the image is removed from `images` entirely to avoid needing to repack it.
|
||||
fn update_vbmeta_headers(
|
||||
images: &mut HashMap<String, Box<dyn ReadSeek + Send>>,
|
||||
images: &mut HashMap<String, PSeekFile>,
|
||||
headers: &mut HashMap<String, Header>,
|
||||
order: &mut [(String, HashSet<String>)],
|
||||
clear_vbmeta_flags: bool,
|
||||
@@ -575,7 +583,9 @@ fn update_vbmeta_headers(
|
||||
.sign(key)
|
||||
.with_context(|| format!("Failed to sign vbmeta header for image: {name}"))?;
|
||||
|
||||
let mut writer = Cursor::new(Vec::new());
|
||||
let mut writer = tempfile::tempfile()
|
||||
.map(PSeekFile::new)
|
||||
.with_context(|| format!("Failed to create temp file for: {name}"))?;
|
||||
parent_header
|
||||
.to_writer(&mut writer)
|
||||
.with_context(|| format!("Failed to write vbmeta image: {name}"))?;
|
||||
@@ -583,7 +593,7 @@ fn update_vbmeta_headers(
|
||||
padding::write_zeros(&mut writer, block_size)
|
||||
.with_context(|| format!("Failed to write vbmeta padding: {name}"))?;
|
||||
|
||||
*images.get_mut(name).unwrap() = Box::new(writer);
|
||||
*images.get_mut(name).unwrap() = writer;
|
||||
} else {
|
||||
unchanged.insert(name.as_str());
|
||||
}
|
||||
@@ -600,17 +610,19 @@ fn update_vbmeta_headers(
|
||||
/// Compress an image and update the OTA manifest partition entry appropriately.
|
||||
fn compress_image(
|
||||
name: &str,
|
||||
mut stream: &mut Box<dyn ReadSeek + Send>,
|
||||
file: &mut PSeekFile,
|
||||
header: &Mutex<PayloadHeader>,
|
||||
block_size: u32,
|
||||
cancel_signal: &AtomicBool,
|
||||
) -> Result<()> {
|
||||
stream.rewind()?;
|
||||
file.rewind()?;
|
||||
|
||||
let writer = Cursor::new(Vec::new());
|
||||
let mut compressed = CompressedPartitionWriter::new(writer, block_size)?;
|
||||
let writer = tempfile::tempfile()
|
||||
.map(PSeekFile::new)
|
||||
.with_context(|| format!("Failed to create temp file for: {name}"))?;
|
||||
|
||||
stream::copy(&mut stream, &mut compressed, cancel_signal)?;
|
||||
let (partition_info, operations) =
|
||||
payload::compress_image(&*file, &writer, name, block_size, cancel_signal)?;
|
||||
|
||||
let mut header_locked = header.lock().unwrap();
|
||||
let partition = header_locked
|
||||
@@ -619,9 +631,11 @@ fn compress_image(
|
||||
.iter_mut()
|
||||
.find(|p| p.partition_name == name)
|
||||
.unwrap();
|
||||
let writer = compressed.finish(partition)?;
|
||||
|
||||
*stream = Box::new(writer);
|
||||
partition.new_partition_info = Some(partition_info);
|
||||
partition.operations = operations;
|
||||
|
||||
*file = writer;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -678,11 +692,11 @@ fn patch_ota_payload(
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
// The set of source images to be inserted into the new payload, replacing
|
||||
// what was in the original payload. Initially, this refers to either real
|
||||
// files on the filesystem (--replace option) or in-memory files (extracted
|
||||
// from the old payload). The values will be replaced later if the images
|
||||
// need to be patched (eg. boot or vbmeta image).
|
||||
let mut input_streams = open_input_streams(
|
||||
// what was in the original payload. Initially, this refers to either user
|
||||
// specified files (--replace option) or temporary files (extracted from the
|
||||
// old payload). The values will be replaced later if the images need to be
|
||||
// patched (eg. boot or vbmeta image).
|
||||
let mut input_files = open_input_files(
|
||||
payload,
|
||||
&required_images,
|
||||
external_images,
|
||||
@@ -692,18 +706,18 @@ fn patch_ota_payload(
|
||||
|
||||
patch_boot_images(
|
||||
&required_images,
|
||||
&mut input_streams,
|
||||
&mut input_files,
|
||||
root_patcher,
|
||||
key_avb,
|
||||
cert_ota,
|
||||
cancel_signal,
|
||||
)?;
|
||||
|
||||
let mut vbmeta_headers = load_vbmeta_images(&mut input_streams, &vbmeta_images)?;
|
||||
let mut vbmeta_headers = load_vbmeta_images(&mut input_files, &vbmeta_images)?;
|
||||
|
||||
ensure_partitions_protected(&header_locked.manifest, &vbmeta_headers)?;
|
||||
|
||||
let mut vbmeta_order = get_vbmeta_patch_order(&mut input_streams, &vbmeta_headers)?;
|
||||
let mut vbmeta_order = get_vbmeta_patch_order(&mut input_files, &vbmeta_headers)?;
|
||||
|
||||
status!(
|
||||
"Patching vbmeta images: {}",
|
||||
@@ -714,12 +728,12 @@ fn patch_ota_payload(
|
||||
for name in &vbmeta_images {
|
||||
// Linear search is fast enough.
|
||||
if !vbmeta_order.iter().any(|v| v.0 == *name) {
|
||||
input_streams.remove(name);
|
||||
input_files.remove(name);
|
||||
}
|
||||
}
|
||||
|
||||
update_vbmeta_headers(
|
||||
&mut input_streams,
|
||||
&mut input_files,
|
||||
&mut vbmeta_headers,
|
||||
&mut vbmeta_order,
|
||||
clear_vbmeta_flags,
|
||||
@@ -729,16 +743,16 @@ fn patch_ota_payload(
|
||||
|
||||
status!(
|
||||
"Compressing replacement images: {}",
|
||||
joined(sorted(input_streams.keys())),
|
||||
joined(sorted(input_files.keys())),
|
||||
);
|
||||
|
||||
let block_size = header_locked.manifest.block_size();
|
||||
drop(header_locked);
|
||||
|
||||
input_streams
|
||||
input_files
|
||||
.par_iter_mut()
|
||||
.map(|(name, stream)| -> Result<()> {
|
||||
compress_image(name, stream, &header, block_size, cancel_signal)
|
||||
.map(|(name, file)| -> Result<()> {
|
||||
compress_image(name, file, &header, block_size, cancel_signal)
|
||||
.with_context(|| format!("Failed to compress image: {name}"))
|
||||
})
|
||||
.collect::<Result<()>>()?;
|
||||
@@ -762,25 +776,31 @@ fn patch_ota_payload(
|
||||
continue;
|
||||
};
|
||||
|
||||
if let Some(mut reader) = input_streams.remove(&name) {
|
||||
// Copy from our replacement image.
|
||||
let pi = payload_writer.partition_index().unwrap();
|
||||
let oi = payload_writer.operation_index().unwrap();
|
||||
let orig_partition = &header_locked.manifest.partitions[pi];
|
||||
let orig_operation = &orig_partition.operations[oi];
|
||||
let data_offset = orig_operation
|
||||
.data_offset
|
||||
.ok_or_else(|| anyhow!("Missing data_offset in partition #{pi} operation #{oi}"))?;
|
||||
|
||||
if let Some(reader) = input_files.get_mut(&name) {
|
||||
// Copy from our replacement image. The compressed chunks are laid
|
||||
// out sequentially and data_offset is set to the offset within that
|
||||
// file.
|
||||
reader
|
||||
.rewind()
|
||||
.seek(SeekFrom::Start(data_offset))
|
||||
.with_context(|| format!("Failed to seek image: {name}"))?;
|
||||
|
||||
stream::copy_n(&mut reader, &mut payload_writer, data_length, cancel_signal)
|
||||
stream::copy_n(reader, &mut payload_writer, data_length, cancel_signal)
|
||||
.with_context(|| format!("Failed to copy from replacement image: {name}"))?;
|
||||
} else {
|
||||
// Copy from the original payload.
|
||||
let pi = payload_writer.partition_index().unwrap();
|
||||
let oi = payload_writer.operation_index().unwrap();
|
||||
let orig_partition = &header_locked.manifest.partitions[pi];
|
||||
let orig_operation = &orig_partition.operations[oi];
|
||||
|
||||
let data_offset = orig_operation
|
||||
.data_offset
|
||||
.and_then(|o| o.checked_add(header_locked.blob_offset))
|
||||
.ok_or_else(|| anyhow!("Missing data_offset in partition #{pi} operation #{oi}"))?;
|
||||
let data_offset = data_offset
|
||||
.checked_add(header_locked.blob_offset)
|
||||
.ok_or_else(|| {
|
||||
anyhow!("data_offset overflow in partition #{pi} operation #{oi}")
|
||||
})?;
|
||||
|
||||
orig_payload_reader
|
||||
.seek(SeekFrom::Start(data_offset))
|
||||
|
||||
@@ -20,7 +20,7 @@ use thiserror::Error;
|
||||
use crate::{
|
||||
format::verityrs,
|
||||
stream::{self, FromReader, ReadSeekReopen, ToWriter, WriteSeekReopen, WriteZerosExt},
|
||||
util::NumBytes,
|
||||
util::{self, NumBytes},
|
||||
};
|
||||
|
||||
// Not to be confused with the 255-byte RS block size.
|
||||
@@ -111,11 +111,6 @@ impl Codeword {
|
||||
}
|
||||
}
|
||||
|
||||
/// Since Rust's built-in .div_ceil() is still nightly-only.
|
||||
fn div_ceil(dividend: u64, divisor: u64) -> u64 {
|
||||
dividend / divisor + u64::from(dividend % divisor != 0)
|
||||
}
|
||||
|
||||
/// A type for performing FEC generation, verification, and error correction for
|
||||
/// a specific file size and Reed Solomon configuration. The implementation uses
|
||||
/// dm-verity's interleaving access pattern.
|
||||
@@ -169,8 +164,8 @@ impl Fec {
|
||||
return Err(Error::UnsupportedParity(parity));
|
||||
}
|
||||
|
||||
let blocks = div_ceil(file_size, u64::from(block_size));
|
||||
let rounds = div_ceil(blocks, u64::from(rs_k));
|
||||
let blocks = util::div_ceil(file_size, u64::from(block_size));
|
||||
let rounds = util::div_ceil(blocks, u64::from(rs_k));
|
||||
|
||||
// Check upfront so we don't need to do checked multiplication later.
|
||||
rounds
|
||||
|
||||
+143
-104
@@ -34,9 +34,10 @@ use crate::{
|
||||
InstallOperation, PartitionInfo, PartitionUpdate, Signatures,
|
||||
},
|
||||
stream::{
|
||||
self, CountingReader, CountingWriter, FromReader, HashingWriter, ReadDiscardExt,
|
||||
ReadSeekReopen, Reopen, SharedCursor, WriteSeek,
|
||||
self, CountingReader, FromReader, HashingWriter, ReadDiscardExt, ReadSeekReopen, WriteSeek,
|
||||
WriteSeekReopen,
|
||||
},
|
||||
util,
|
||||
};
|
||||
|
||||
const OTA_MAGIC: &[u8; 4] = b"CrAU";
|
||||
@@ -535,98 +536,6 @@ impl<W: Write> Write for PayloadWriter<W> {
|
||||
}
|
||||
}
|
||||
|
||||
/// A writer to produce a compressed partition image suitable for directly
|
||||
/// inserting into a `payload.bin`'s blob section. The data is XZ-compressed at
|
||||
/// a low compression level, primarily to collapse zeros, and the
|
||||
/// [`PartitionUpdate`] instance is updated with the final size and hash. The
|
||||
/// entire data will be represented as a single [`InstallOperation`] and
|
||||
/// [`InstallOperation::data_offset`] will be set to `None`.
|
||||
pub struct CompressedPartitionWriter<W: Write> {
|
||||
inner: XzEncoder<HashingWriter<CountingWriter<W>>>,
|
||||
block_size: u32,
|
||||
h_uncompressed: Context,
|
||||
written: u64,
|
||||
}
|
||||
|
||||
impl<W: Write> CompressedPartitionWriter<W> {
|
||||
pub fn new(writer: W, block_size: u32) -> Result<Self> {
|
||||
let counting_writer = CountingWriter::new(writer);
|
||||
let hashing_writer =
|
||||
HashingWriter::new(counting_writer, Context::new(&ring::digest::SHA256));
|
||||
|
||||
// AOSP's payload_consumer does not support CRC during decompression.
|
||||
// Also, we intentionally pick the lowest compression level since we
|
||||
// primarily care about squishing zeros. The non-zero portions of boot
|
||||
// images are usually already-compressed kernels and ramdisks.
|
||||
let stream = Stream::new_easy_encoder(0, Check::None)?;
|
||||
let xz_writer = XzEncoder::new_stream(hashing_writer, stream);
|
||||
|
||||
Ok(Self {
|
||||
inner: xz_writer,
|
||||
block_size,
|
||||
h_uncompressed: Context::new(&ring::digest::SHA256),
|
||||
written: 0,
|
||||
})
|
||||
}
|
||||
|
||||
/// Finish writing and update the [`PartitionUpdate`] instance with the new
|
||||
/// size, hash, and install operation metadata.
|
||||
pub fn finish(mut self, partition: &mut PartitionUpdate) -> Result<W> {
|
||||
if self.written % u64::from(self.block_size) != 0 {
|
||||
return Err(Error::InvalidPartitionSize {
|
||||
name: partition.partition_name.clone(),
|
||||
size: self.written,
|
||||
block_size: self.block_size,
|
||||
});
|
||||
}
|
||||
|
||||
self.inner.flush()?;
|
||||
|
||||
let hashing_writer = self.inner.finish()?;
|
||||
let digest_uncompressed = self.h_uncompressed.finish();
|
||||
let (counting_writer, context_compressed) = hashing_writer.finish();
|
||||
let digest_compressed = context_compressed.finish();
|
||||
// XzEncoder::total_out() cannot be used for an exact byte count because
|
||||
// XzEncoder::finish() writes data.
|
||||
let (writer, size_compressed) = counting_writer.finish();
|
||||
|
||||
partition.new_partition_info = Some(PartitionInfo {
|
||||
size: Some(self.written),
|
||||
hash: Some(digest_uncompressed.as_ref().to_vec()),
|
||||
});
|
||||
|
||||
let extent = Extent {
|
||||
start_block: Some(0),
|
||||
num_blocks: Some(self.written / u64::from(self.block_size)),
|
||||
};
|
||||
|
||||
// data_offset must be manually updated by the caller.
|
||||
let mut operation = InstallOperation::default();
|
||||
operation.set_type(Type::ReplaceXz);
|
||||
operation.data_length = Some(size_compressed);
|
||||
operation.dst_extents.push(extent);
|
||||
operation.data_sha256_hash = Some(digest_compressed.as_ref().to_vec());
|
||||
|
||||
partition.operations.clear();
|
||||
partition.operations.push(operation);
|
||||
|
||||
Ok(writer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> Write for CompressedPartitionWriter<W> {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
let n = self.inner.write(buf)?;
|
||||
self.h_uncompressed.update(&buf[..n]);
|
||||
self.written += n as u64;
|
||||
Ok(n)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
self.inner.flush()
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify the payload signatures using the specified certificate and check that
|
||||
/// the digests in `payload_properties.txt` are correct.
|
||||
pub fn verify_payload(
|
||||
@@ -867,29 +776,29 @@ pub fn apply_operation(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Extract the specified image from the payload into memory. This is done
|
||||
/// multithreaded and uses rayon's global thread pool. `open_payload` will be
|
||||
/// called from multiple threads.
|
||||
pub fn extract_image_to_memory(
|
||||
/// Extract the specified image from the payload. This is done multithreaded and
|
||||
/// uses rayon's global thread pool. Both the `payload` and `output` streams
|
||||
/// will be reopened from multiple threads.
|
||||
pub fn extract_image(
|
||||
payload: &(dyn ReadSeekReopen + Sync),
|
||||
output: &(dyn WriteSeekReopen + Sync),
|
||||
header: &PayloadHeader,
|
||||
partition_name: &str,
|
||||
cancel_signal: &AtomicBool,
|
||||
) -> Result<SharedCursor> {
|
||||
) -> Result<()> {
|
||||
let partition = header
|
||||
.manifest
|
||||
.partitions
|
||||
.iter()
|
||||
.find(|p| p.partition_name == partition_name)
|
||||
.ok_or_else(|| Error::MissingPartition(partition_name.to_owned()))?;
|
||||
let stream = SharedCursor::default();
|
||||
|
||||
partition
|
||||
.operations
|
||||
.par_iter()
|
||||
.map(|op| -> Result<()> {
|
||||
let reader = payload.reopen_boxed()?;
|
||||
let writer = stream.reopen()?;
|
||||
let writer = output.reopen_boxed()?;
|
||||
|
||||
apply_operation(
|
||||
reader,
|
||||
@@ -902,9 +811,7 @@ pub fn extract_image_to_memory(
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.collect::<Result<_>>()?;
|
||||
|
||||
Ok(stream)
|
||||
.collect::<Result<_>>()
|
||||
}
|
||||
|
||||
/// Extract the specified partition images from the payload into writers. This
|
||||
@@ -954,3 +861,135 @@ pub fn extract_images<'a>(
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Compress the image and return the corresponding information to insert into
|
||||
/// the payload manifest's [`PartitionUpdate`] instance. The uncompressed data
|
||||
/// is split into 2 MiB chunks, which are read and compressed in parallel, and
|
||||
/// then written in parallel (but in order) to the output. Each chunk will have
|
||||
/// a corresponding [`InstallOperation`] in the return value. The caller must
|
||||
/// update [`InstallOperation::data_offset`] in each operation manually because
|
||||
/// the initial values are relative to 0.
|
||||
pub fn compress_image(
|
||||
input: &(dyn ReadSeekReopen + Sync),
|
||||
output: &(dyn WriteSeekReopen + Sync),
|
||||
partition_name: &str,
|
||||
block_size: u32,
|
||||
cancel_signal: &AtomicBool,
|
||||
) -> Result<(PartitionInfo, Vec<InstallOperation>)> {
|
||||
const CHUNK_SIZE: u64 = 2 * 1024 * 1024;
|
||||
const CHUNK_GROUP: u64 = 32;
|
||||
|
||||
let file_size = input.reopen_boxed()?.seek(SeekFrom::End(0))?;
|
||||
let final_chunk_different = file_size % CHUNK_SIZE != 0;
|
||||
|
||||
if file_size % u64::from(block_size) != 0 || CHUNK_SIZE % u64::from(block_size) != 0 {
|
||||
return Err(Error::InvalidPartitionSize {
|
||||
name: partition_name.to_owned(),
|
||||
size: file_size,
|
||||
block_size,
|
||||
});
|
||||
}
|
||||
|
||||
let chunks_total = util::div_ceil(file_size, CHUNK_SIZE);
|
||||
let mut bytes_compressed = 0;
|
||||
let mut context_uncompressed = Context::new(&ring::digest::SHA256);
|
||||
let mut operations = vec![];
|
||||
|
||||
// Read the file one group at a time. This allows for some parallelization
|
||||
// without reading the entire file into memory. This is necessary because we
|
||||
// need to compute the checksum of the entire file.
|
||||
while (operations.len() as u64) < chunks_total {
|
||||
let chunks_done = operations.len() as u64;
|
||||
let chunks_group = (chunks_total - chunks_done).min(CHUNK_GROUP);
|
||||
|
||||
let uncompressed_data_group = (chunks_done..chunks_done + chunks_group)
|
||||
.into_par_iter()
|
||||
.map(|chunk| -> Result<(u64, Vec<u8>)> {
|
||||
let mut reader = input.reopen_boxed()?;
|
||||
let offset = reader.seek(SeekFrom::Start(chunk * CHUNK_SIZE))?;
|
||||
|
||||
let chunk_size = if final_chunk_different && chunk == chunks_total - 1 {
|
||||
file_size % CHUNK_SIZE
|
||||
} else {
|
||||
CHUNK_SIZE
|
||||
};
|
||||
let mut data = vec![0u8; chunk_size as usize];
|
||||
|
||||
stream::check_cancel(cancel_signal)?;
|
||||
reader.read_exact(&mut data)?;
|
||||
|
||||
Ok((offset, data))
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
|
||||
for (_, data) in &uncompressed_data_group {
|
||||
context_uncompressed.update(data);
|
||||
}
|
||||
|
||||
let mut compressed_data_group = uncompressed_data_group
|
||||
.into_par_iter()
|
||||
.map(
|
||||
|(raw_offset, raw_data)| -> Result<(Vec<u8>, InstallOperation)> {
|
||||
let reader = Cursor::new(raw_data.as_slice());
|
||||
let writer = Cursor::new(Vec::new());
|
||||
let hashing_writer =
|
||||
HashingWriter::new(writer, Context::new(&ring::digest::SHA256));
|
||||
|
||||
// AOSP's payload_consumer does not support checking CRC during
|
||||
// decompression. Also, we intentionally pick the lowest
|
||||
// compression level since we primarily care about squishing
|
||||
// zeros. The non-zero portions of boot images are usually
|
||||
// already-compressed kernels and ramdisks.
|
||||
let stream = Stream::new_easy_encoder(0, Check::None)?;
|
||||
let mut xz_writer = XzEncoder::new_stream(hashing_writer, stream);
|
||||
|
||||
stream::copy_n(reader, &mut xz_writer, raw_data.len() as u64, cancel_signal)?;
|
||||
|
||||
let hashing_writer = xz_writer.finish()?;
|
||||
let (writer, context_compressed) = hashing_writer.finish();
|
||||
let digest_compressed = context_compressed.finish();
|
||||
let data = writer.into_inner();
|
||||
|
||||
let extent = Extent {
|
||||
start_block: Some(raw_offset / u64::from(block_size)),
|
||||
num_blocks: Some(raw_data.len() as u64 / u64::from(block_size)),
|
||||
};
|
||||
|
||||
let mut operation = InstallOperation::default();
|
||||
operation.set_type(Type::ReplaceXz);
|
||||
operation.data_length = Some(data.len() as u64);
|
||||
operation.dst_extents.push(extent);
|
||||
operation.data_sha256_hash = Some(digest_compressed.as_ref().to_vec());
|
||||
|
||||
Ok((data, operation))
|
||||
},
|
||||
)
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
|
||||
for (data, operation) in &mut compressed_data_group {
|
||||
operation.data_offset = Some(bytes_compressed);
|
||||
bytes_compressed += data.len() as u64;
|
||||
}
|
||||
|
||||
let group_operations = compressed_data_group
|
||||
.into_par_iter()
|
||||
.map(|(data, operation)| -> Result<InstallOperation> {
|
||||
let mut writer = output.reopen_boxed()?;
|
||||
writer.seek(SeekFrom::Start(operation.data_offset.unwrap()))?;
|
||||
writer.write_all(&data)?;
|
||||
|
||||
Ok(operation)
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
|
||||
operations.extend(group_operations.into_iter());
|
||||
}
|
||||
|
||||
let digest_uncompressed = context_uncompressed.finish();
|
||||
let partition_info = PartitionInfo {
|
||||
size: Some(file_size),
|
||||
hash: Some(digest_uncompressed.as_ref().to_vec()),
|
||||
};
|
||||
|
||||
Ok((partition_info, operations))
|
||||
}
|
||||
|
||||
@@ -48,3 +48,13 @@ pub fn parent_path(path: &Path) -> &Path {
|
||||
|
||||
Path::new(".")
|
||||
}
|
||||
|
||||
/// Since Rust's built-in .div_ceil() is still nightly-only.
|
||||
pub fn div_ceil<T: PrimInt>(dividend: T, divisor: T) -> T {
|
||||
dividend / divisor
|
||||
+ if dividend % divisor != T::zero() {
|
||||
T::one()
|
||||
} else {
|
||||
T::zero()
|
||||
}
|
||||
}
|
||||
|
||||
+10
-10
@@ -15,8 +15,8 @@ sections = [
|
||||
]
|
||||
hash.original.full = "6b881553f012d582080642d660e1cf5c9e6fe41e9f1c6ab12ae87fab7894e307"
|
||||
hash.original.stripped = "9befd7887a125ebd8e9ae0555469dababe6bc04b0aa41aa2562036782a6d87e0"
|
||||
hash.patched.full = "91e15447ade648c10bce599e75569ce55edc18798b11a704586acf3b51ae7971"
|
||||
hash.patched.stripped = "84b069ac7f20115ac0b19d7e319bb86d00b9f3f64e0c7a51612f99d6cae297bb"
|
||||
hash.patched.full = "8de1892395be6173687ce6333d926394ca486c61856dd7585c6a7fc4feb0624e"
|
||||
hash.patched.stripped = "d24efed2dd68625fa8f4b44149dd07fcb5597f925e16b62a9e968edd15115fea"
|
||||
hash.avb_images."init_boot.img" = "3bedb41be98c46241f11219021dfbb799a6d5c89e6e00d45a66f9a5b42e7dfbc"
|
||||
hash.avb_images."vbmeta.img" = "e8e6e898ca73807edb43af0a0e86d4a94b14256def89581970287a9b1bf7a3ee"
|
||||
hash.avb_images."vbmeta_system.img" = "dbb63e08f26f46ccda501d99058d513ff71e3d6302c14d587442b666ff08862a"
|
||||
@@ -36,8 +36,8 @@ sections = [
|
||||
]
|
||||
hash.original.full = "1f1f0abe67a6f6f47287be6dafec2c12628de6a715b82ca7beddaf67ad22aca5"
|
||||
hash.original.stripped = "38b15f5efdc7e056bc799859ba72ef9a73e93c61292c59f85fb4b9c31acc5f82"
|
||||
hash.patched.full = "65f7e29591fb48ad9c7c3233df4d76bb6986aef96b94c06b73213477bc7486d8"
|
||||
hash.patched.stripped = "cf77b5e307ce4d2a62e742cd3a300964de6c693880cbfa90dcd0bfec66bd1976"
|
||||
hash.patched.full = "6916e91351e464339f63be311f8debc9a54d2e3c5509a6fe9c886f0ee63a556c"
|
||||
hash.patched.stripped = "be40b24c23cefb5ab5704420a6d93dc4f168d38124aee3c663ebe9e2f9a345f0"
|
||||
hash.avb_images."boot.img" = "a19cb4d4fcc7f3e7d3046c3d19e2f243fb02513ca848ff92ad70d1ada55c4e65"
|
||||
hash.avb_images."vbmeta.img" = "2d817e35f7b6cdc2edce58ef249a966fc677085b70a4aced4c829320aeef0be2"
|
||||
hash.avb_images."vbmeta_system.img" = "98a050f0d53a016fbb78147b1b4a9bca3fde615aa4da34bf62c2e07a395104b5"
|
||||
@@ -57,8 +57,8 @@ sections = [
|
||||
]
|
||||
hash.original.full = "6d107ffac1cd3da2c972112acc75957ed725e5c13d57ca724d9bcca5404fcebd"
|
||||
hash.original.stripped = "5b889bdab3bb12ddcd3c243a56e1c58bedada8831069f49d56fe5098fb141e35"
|
||||
hash.patched.full = "8725e03798539070d7075a07c80fb1403652a2b446d5c460d636a32b7368c9a2"
|
||||
hash.patched.stripped = "c4b5cfa84dc8c15f3ac9661d768062546fa36ca82cdeb6dd943347be0422903c"
|
||||
hash.patched.full = "67e1fbc5b1c189cb856d9fd9d846a780ba42e398c5d2e161f4ca89ffccecc32f"
|
||||
hash.patched.stripped = "37280d66e316b72f223fb06a2a004902bbf8a28c0da3d9115af12b0d8aea5186"
|
||||
hash.avb_images."boot.img" = "8e7278a2e8ae44ffc5475717eb0e1aa56bfb7650aef34375b0fe92f790835f95"
|
||||
hash.avb_images."vbmeta.img" = "b036132b867f52a86eef79716261f35b1eb50e843dc4fe42f72cce67b24ae2db"
|
||||
hash.avb_images."vbmeta_system.img" = "9a7c6fd654e7a92aeffbdbd55ea0d87eee36f4c235e1b505423ad8a13a751a00"
|
||||
@@ -76,8 +76,8 @@ sections = [
|
||||
]
|
||||
hash.original.full = "01fd34b206152a3559039161c9874ab03df37da4268b86a9e0be899de5fc0af7"
|
||||
hash.original.stripped = "cc311b5bd46e06cfdefbade794d33aa9bc3ceda4ad4f38bfe9f0dfc17033d207"
|
||||
hash.patched.full = "f2ac798b31a94dc251ca4ce370ebfb3073170d4a34431829df9ed0742149ffe7"
|
||||
hash.patched.stripped = "4387a5ba30c925f56c67eeaf6512757d5c1e07dfd2a8a99be14e06db8ba2dde7"
|
||||
hash.patched.full = "dc56c61170c0de6e2bb2cf8c7c0b6aa810f0aaea1eb045b3d4b1643482bb27fd"
|
||||
hash.patched.stripped = "1b6b1818ff72d79e9a1a3f376beca5d62ea2715cbd97a3997f58ca636b588bb4"
|
||||
hash.avb_images."boot.img" = "506a955080b6cfa2039ef85923e8a4e717ef6c1dc478538599c7ba26ee21e525"
|
||||
hash.avb_images."vbmeta.img" = "3679c7224e3e3e0793b4d1a031e116098460a6b4c5f3f88d5a3a0a4c65b21582"
|
||||
hash.avb_images."vbmeta_system.img" = "1d3efa00fd1d44a594c7317072468fa95c23d83d2759d6d6e757783ceeabc594"
|
||||
@@ -107,8 +107,8 @@ sections = [
|
||||
]
|
||||
hash.original.full = "929f892fbd70699cf7f118a119aac1ae1b86351e1ada17715666fa4401e63472"
|
||||
hash.original.stripped = "4eabaf79b6c2b5df305e3ecdc2b9570c0dd27350b4e8d6434584000c4989ff3d"
|
||||
hash.patched.full = "ec576b7430e5a2788e89f8bbe37ee81b116386d9b101865d3b75eb9c51a0db4c"
|
||||
hash.patched.stripped = "ddd0cf1b42564f8c2980320c14f8e0eb125412eb7a13ae8fb52d35424546c235"
|
||||
hash.patched.full = "fb61ac143024dd9c01d3af45fdeacde078c46c4c7f1d6d2d6da24561d555ad46"
|
||||
hash.patched.stripped = "a222904445c2d17c203f34a113da1c4657c622b38d32cde21487ab5ad6b8afb3"
|
||||
hash.avb_images."boot.img" = "480d7cf519326fcaa5106ecfbbeb907309068635f7f6a25e5e4571d525c4926a"
|
||||
hash.avb_images."recovery.img" = "eeb0f67e2084174fced510f4b59a82022559ffbe1c20d2c0ea4757fcd989a3af"
|
||||
hash.avb_images."vbmeta.img" = "c022cf79da301a8430af5c49704944c490707fa0306031fe3ea22c39ce4734f6"
|
||||
|
||||
Reference in New Issue
Block a user