android-sparse 0.3.1

An implementation of Android's sparse file format.
Documentation
extern crate android_sparse as sparse;
#[macro_use]
extern crate clap;

use std::fs::File;
use std::io::{Seek, SeekFrom};

use clap::{App, Arg, ArgMatches};

use sparse::constants::{BLOCK_SIZE, CHUNK_HEADER_SIZE, FILE_HEADER_SIZE};
use sparse::result::Result;

#[derive(PartialEq)]
enum Verbosity {
    Normal,
    Verbose,
}

fn parse_args<'a>() -> ArgMatches<'a> {
    App::new("simg_dump")
        .about("Display sparse file info")
        .version(crate_version!())
        .author("Jan Teske <jan.teske@gmail.com>")
        .arg(Arg::with_name("sparse_file").required(true))
        .arg(
            Arg::with_name("verbose")
                .help("Verbose output")
                .short("v")
                .long("verbose"),
        )
        .get_matches()
}

fn simg_dump(args: &ArgMatches) -> Result<()> {
    let verbosity = match args.is_present("verbose") {
        true => Verbosity::Verbose,
        false => Verbosity::Normal,
    };

    let mut fi = File::open(&args.value_of("sparse_file").unwrap())?;
    let mut sparse_file = sparse::File::new();

    let read_result = sparse::Reader::new(fi.try_clone()?).read(&mut sparse_file);
    // Even if there was an error during parsing, we may still have useful
    // information, so we dump it anyway.
    dump(&sparse_file, verbosity);
    read_result?;

    let spf_end = fi.seek(SeekFrom::Current(0))?;
    let file_end = fi.seek(SeekFrom::End(0))?;
    if spf_end != file_end {
        println!(
            "There are {} bytes of extra data at the end of the file.",
            file_end - spf_end
        );
    }

    Ok(())
}

fn dump(spf: &sparse::File, verbosity: Verbosity) {
    dump_summary(spf);
    if verbosity == Verbosity::Verbose {
        dump_chunks(spf);
    }
}


fn dump_summary(spf: &sparse::File) {
    println!(
        "Total of {} {}-byte output blocks in {} input chunks.",
        spf.num_blocks(),
        BLOCK_SIZE,
        spf.num_chunks()
    );

    if spf.checksum() != 0 {
        println!("checksum=0x{:>08x}", spf.checksum());
    }
}


fn dump_chunks(spf: &sparse::File) {
    println!("");
    println!("       |       input_bytes       |   output_blocks   |");
    println!(" chunk |   offset   |   number   | offset  |  number | type");
    println!("-----------------------------------------------------------------");

    let mut bytes_off = u64::from(FILE_HEADER_SIZE + CHUNK_HEADER_SIZE);
    let mut blocks_off = 0_u64;
    for (i, chunk) in spf.chunk_iter().enumerate() {
        let chunk_num = i + 1;
        let sparse_size = chunk.sparse_size() - u32::from(CHUNK_HEADER_SIZE);
        let num_blocks = chunk.num_blocks();

        println!(
            " {:>5} | {:>10} | {:>10} | {:>7} | {:>7} | {}",
            chunk_num,
            bytes_off,
            sparse_size,
            blocks_off,
            num_blocks,
            chunk_type_str(chunk),
        );

        bytes_off += u64::from(sparse_size) + u64::from(CHUNK_HEADER_SIZE);
        blocks_off += u64::from(num_blocks);
    }

    println!("");
}

fn chunk_type_str(chunk: &sparse::file::Chunk) -> String {
    use sparse::file::Chunk::*;

    match *chunk {
        Raw { .. } => "raw".into(),
        Fill { fill, .. } => format!(
            "fill: \\x{:>02x}\\x{:>02x}\\x{:>02x}\\x{:>02x}",
            fill[0],
            fill[1],
            fill[2],
            fill[3]
        ),
        DontCare { .. } => "dont_care".into(),
        Crc32 { crc } => format!("crc32: 0x{:>08x}", crc),
    }
}

fn main() {
    let args = parse_args();
    simg_dump(&args).unwrap_or_else(|err| eprintln!("error: {}", err));
}