Properly support streamable input

This commit is contained in:
topjohnwu
2023-05-26 14:07:11 -07:00
parent 659b9c6fee
commit 5b8b48ccc1
5 changed files with 239 additions and 175 deletions

View File

@@ -1,8 +1,9 @@
use mem::MaybeUninit;
use std::cmp::min;
use std::ffi::CStr;
use std::io;
use std::io::{BufRead, Write};
use std::io::{BufRead, Read, Seek, SeekFrom, Write};
use std::os::unix::io::{AsRawFd, FromRawFd, OwnedFd, RawFd};
use std::{io, mem};
use libc::{c_char, c_uint, mode_t, EEXIST, ENOENT, O_CLOEXEC, O_PATH};
@@ -135,6 +136,37 @@ pub extern "C" fn mkdirs(path: *const c_char, mode: mode_t) -> i32 {
}
}
pub trait ReadExt {
fn skip(&mut self, len: usize) -> io::Result<()>;
}
impl<T: Read> ReadExt for T {
fn skip(&mut self, mut len: usize) -> io::Result<()> {
let mut buf = MaybeUninit::<[u8; 4096]>::uninit();
let buf = unsafe { buf.assume_init_mut() };
while len > 0 {
let l = min(buf.len(), len);
self.read_exact(&mut buf[..l])?;
len -= l;
}
Ok(())
}
}
pub trait ReadSeekExt {
fn skip(&mut self, len: usize) -> io::Result<()>;
}
impl<T: Read + Seek> ReadSeekExt for T {
fn skip(&mut self, len: usize) -> io::Result<()> {
if self.seek(SeekFrom::Current(len as i64)).is_err() {
// If the file is not actually seekable, fallback to read
ReadExt::skip(self, len)?;
}
Ok(())
}
}
pub trait BufReadExt {
fn foreach_lines<F: FnMut(&mut String) -> bool>(&mut self, f: F);
fn foreach_props<F: FnMut(&str, &str) -> bool>(&mut self, f: F);

View File

@@ -51,7 +51,7 @@ Supported actions:
If [partition] is not specified, then attempt to extract either
'init_boot' or 'boot'. Which partition was chosen can be determined
by whichever 'init_boot.img' or 'boot.img' exists.
<payload.bin>/[outfile] can be '-' to be STDIN/STDOUT.
<payload.bin> can be '-' to be STDIN.
hexpatch <file> <hexpattern1> <hexpattern2>
Search <hexpattern1> in <file>, and replace it with <hexpattern2>

View File

@@ -7,7 +7,7 @@ use byteorder::{BigEndian, ReadBytesExt};
use protobuf::{EnumFull, Message};
use base::libc::c_char;
use base::{ptr_to_str_result, StrErr};
use base::{ptr_to_str_result, ReadSeekExt, StrErr};
use base::{ResultExt, WriteExt};
use crate::ffi;
@@ -27,7 +27,7 @@ const PAYLOAD_MAGIC: &str = "CrAU";
fn do_extract_boot_from_payload(
in_path: &str,
partition: Option<&str>,
partition_name: Option<&str>,
out_path: Option<&str>,
) -> anyhow::Result<()> {
let mut reader = BufReader::new(if in_path == "-" {
@@ -74,46 +74,50 @@ fn do_extract_boot_from_payload(
let block_size = manifest.block_size() as u64;
let part = match partition {
let partition = match partition_name {
None => {
let boot = manifest
.partitions
.iter()
.find(|partition| partition.partition_name() == "init_boot");
.find(|p| p.partition_name() == "init_boot");
let boot = match boot {
Some(boot) => Some(boot),
None => manifest
.partitions
.iter()
.find(|partition| partition.partition_name() == "boot"),
.find(|p| p.partition_name() == "boot"),
};
boot.ok_or(anyhow!("boot partition not found"))?
}
Some(partition) => manifest
Some(name) => manifest
.partitions
.iter()
.find(|p| p.partition_name() == partition)
.ok_or(anyhow!("partition '{partition}' not found"))?,
.find(|p| p.partition_name() == name)
.ok_or(anyhow!("partition '{name}' not found"))?,
};
let out_str: String;
let out_path = match out_path {
None => {
out_str = format!("{}.img", part.partition_name());
out_str = format!("{}.img", partition.partition_name());
out_str.as_str()
}
Some(p) => p,
Some(s) => s,
};
let mut out_file = if out_path == "-" {
unsafe { File::from_raw_fd(1) }
} else {
File::create(out_path).with_context(|| format!("cannot write to '{out_path}'"))?
};
let mut out_file =
File::create(out_path).with_context(|| format!("cannot write to '{out_path}'"))?;
let base_offset = reader.stream_position()? + manifest_sig_len as u64;
// Skip the manifest signature
reader.skip(manifest_sig_len as usize)?;
for operation in part.operations.iter() {
// Sort the install operations with data_offset so we will only ever need to seek forward
// This makes it possible to support non-seekable input file descriptors
let mut operations = partition.operations.clone();
operations.sort_by_key(|e| e.data_offset.unwrap_or(0));
let mut curr_data_offset: u64 = 0;
for operation in operations.iter() {
let data_len = operation
.data_length
.ok_or(bad_payload!("data length not found"))? as usize;
@@ -131,8 +135,11 @@ fn do_extract_boot_from_payload(
buf.resize(data_len, 0u8);
let data = &mut buf[..data_len];
reader.seek(SeekFrom::Start(base_offset + data_offset))?;
// Skip to the next offset and read data
let skip = data_offset - curr_data_offset;
reader.skip(skip as usize)?;
reader.read_exact(data)?;
curr_data_offset = data_offset + data_len as u64;
let out_offset = operation
.dst_extents