summary refs log blame commit diff
path: root/qcow_utils/src/qcow_img.rs
blob: 4f8be58ed079e1fad5118cfbbc7a51ebe040a41b (plain) (tree)










































                                                                                                 



                                                                                





















































                                                                                 







                                                                       














































































































































































                                                                                                  









































                                                                           
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

extern crate getopts;
extern crate qcow;
extern crate sys_util;

use std::fs::OpenOptions;
use std::io::{Read, Write};

use getopts::Options;

use qcow::QcowFile;
use sys_util::WriteZeroes;

fn show_usage(program_name: &str) {
    println!("Usage: {} [subcommand] <subcommand args>", program_name);
    println!("\nSubcommands:");
    println!(
        "{} header <file name> - Show the qcow2 header for a file.",
        program_name
    );
    println!(
        "{} l1_table <file name> - Show the L1 table entries for a file.",
        program_name
    );
    println!(
        "{} l22table <file name> <l1 index> - Show the L2 table pointed to by the nth L1 entry.",
        program_name
    );
    println!(
        "{} ref_table <file name> - Show the refblock table for the file.",
        program_name
    );
    println!(
        "{} ref_block <file_name> <table index> - Show the nth reblock in the file.",
        program_name
    );
    println!(
        "{} dd <file_name> <source_file> - Write bytes from the raw source_file to the file.",
        program_name
    );
    println!(
        "{} convert <src_file> <dst_file> - Convert from src_file to dst_file.",
        program_name
    );
}

fn main() -> std::result::Result<(), ()> {
    let args: Vec<String> = std::env::args().collect();
    let opts = Options::new();

    let matches = match opts.parse(&args[1..]) {
        Ok(m) => m,
        Err(f) => panic!(f.to_string()),
    };

    if matches.free.len() < 2 {
        println!("Must specify the subcommand and the QCOW file to operate on.");
        show_usage(&args[0]);
        return Err(());
    }

    match matches.free[0].as_ref() {
        "header" => show_header(&matches.free[1]),
        "help" => {
            show_usage(&args[0]);
            Ok(())
        }
        "l1_table" => show_l1_table(&matches.free[1]),
        "l2_table" => {
            if matches.free.len() < 2 {
                println!("Filename and table index are required.");
                show_usage(&args[0]);
                return Err(());
            }
            show_l2_table(&matches.free[1], matches.free[2].parse().unwrap())
        }
        "ref_table" => show_ref_table(&matches.free[1]),
        "ref_block" => {
            if matches.free.len() < 2 {
                println!("Filename and block index are required.");
                show_usage(&args[0]);
                return Err(());
            }
            show_ref_block(&matches.free[1], matches.free[2].parse().unwrap())
        }
        "dd" => {
            if matches.free.len() < 2 {
                println!("Qcow and source file are required.");
                show_usage(&args[0]);
                return Err(());
            }
            let count = if matches.free.len() > 3 {
                Some(matches.free[3].parse().unwrap())
            } else {
                None
            };
            dd(&matches.free[1], &matches.free[2], count)
        }
        "convert" => {
            if matches.free.len() < 2 {
                println!("Source and destination files are required.");
                show_usage(&args[0]);
                return Err(());
            }
            convert(&matches.free[1], &matches.free[2])
        }
        c => {
            println!("invalid subcommand: {:?}", c);
            Err(())
        }
    }
}

fn show_header(file_path: &str) -> std::result::Result<(), ()> {
    let file = match OpenOptions::new().read(true).open(file_path) {
        Ok(f) => f,
        Err(_) => {
            println!("Failed to open {}", file_path);
            return Err(());
        }
    };

    let qcow_file = QcowFile::from(file).map_err(|_| ())?;
    let header = qcow_file.header();

    println!("magic {:x}", header.magic);
    println!("version {:x}", header.version);
    println!("backing_file_offset {:x}", header.backing_file_offset);
    println!("backing_file_size {:x}", header.backing_file_size);
    println!("cluster_bits {:x}", header.cluster_bits);
    println!("size {:x}", header.size);
    println!("crypt_method {:x}", header.crypt_method);
    println!("l1_size {:x}", header.l1_size);
    println!("l1_table_offset {:x}", header.l1_table_offset);
    println!("refcount_table_offset {:x}", header.refcount_table_offset);
    println!(
        "refcount_table_clusters {:x}",
        header.refcount_table_clusters
    );
    println!("nb_snapshots {:x}", header.nb_snapshots);
    println!("snapshots_offset {:x}", header.snapshots_offset);
    println!("incompatible_features {:x}", header.incompatible_features);
    println!("compatible_features {:x}", header.compatible_features);
    println!("autoclear_features {:x}", header.autoclear_features);
    println!("refcount_order {:x}", header.refcount_order);
    println!("header_size {:x}", header.header_size);
    Ok(())
}

fn show_l1_table(file_path: &str) -> std::result::Result<(), ()> {
    let file = match OpenOptions::new().read(true).open(file_path) {
        Ok(f) => f,
        Err(_) => {
            println!("Failed to open {}", file_path);
            return Err(());
        }
    };

    let qcow_file = QcowFile::from(file).map_err(|_| ())?;
    let l1_table = qcow_file.l1_table();

    for (i, l2_offset) in l1_table.iter().enumerate() {
        println!("{}: {:x}", i, l2_offset);
    }

    Ok(())
}

fn show_l2_table(file_path: &str, index: usize) -> std::result::Result<(), ()> {
    let file = match OpenOptions::new().read(true).open(file_path) {
        Ok(f) => f,
        Err(_) => {
            println!("Failed to open {}", file_path);
            return Err(());
        }
    };

    let mut qcow_file = QcowFile::from(file).map_err(|_| ())?;
    let l2_table = qcow_file.l2_table(index).unwrap();

    if let Some(cluster_addrs) = l2_table {
        for (i, addr) in cluster_addrs.iter().enumerate() {
            if i % 16 == 0 {
                print!("\n{:x}:", i);
            }
            print!(" {:x}", addr);
        }
    }

    Ok(())
}

fn show_ref_table(file_path: &str) -> std::result::Result<(), ()> {
    let file = match OpenOptions::new().read(true).open(file_path) {
        Ok(f) => f,
        Err(_) => {
            println!("Failed to open {}", file_path);
            return Err(());
        }
    };

    let qcow_file = QcowFile::from(file).map_err(|_| ())?;
    let ref_table = qcow_file.ref_table();

    for (i, block_offset) in ref_table.iter().enumerate() {
        println!("{}: {:x}", i, block_offset);
    }

    Ok(())
}

fn show_ref_block(file_path: &str, index: usize) -> std::result::Result<(), ()> {
    let file = match OpenOptions::new().read(true).open(file_path) {
        Ok(f) => f,
        Err(_) => {
            println!("Failed to open {}", file_path);
            return Err(());
        }
    };

    let mut qcow_file = QcowFile::from(file).map_err(|_| ())?;
    let ref_table = qcow_file.refcount_block(index).unwrap();

    if let Some(counts) = ref_table {
        for (i, count) in counts.iter().enumerate() {
            if i % 16 == 0 {
                print!("\n{:x}:", i);
            }
            print!(" {:x}", count);
        }
    }

    Ok(())
}

// Transfers from a raw file specifiec in `source_path` to the qcow file specified in `file_path`.
fn dd(file_path: &str, source_path: &str, count: Option<usize>) -> std::result::Result<(), ()> {
    let file = match OpenOptions::new().read(true).write(true).open(file_path) {
        Ok(f) => f,
        Err(_) => {
            println!("Failed to open {}", file_path);
            return Err(());
        }
    };

    let mut qcow_file = QcowFile::from(file).map_err(|_| ())?;

    let mut src_file = match OpenOptions::new().read(true).open(source_path) {
        Ok(f) => f,
        Err(_) => {
            println!("Failed to open {}", file_path);
            return Err(());
        }
    };

    let mut read_count = 0;
    const CHUNK_SIZE: usize = 65536;
    let mut buf = [0; CHUNK_SIZE];
    loop {
        let this_count = if let Some(count) = count {
            std::cmp::min(CHUNK_SIZE, count - read_count)
        } else {
            CHUNK_SIZE
        };
        let nread = src_file.read(&mut buf[..this_count]).map_err(|_| ())?;
        // If this block is all zeros, then use write_zeros so the output file is sparse.
        if buf.iter().all(|b| *b == 0) {
            qcow_file.write_zeroes(CHUNK_SIZE).map_err(|_| ())?;
        } else {
            qcow_file.write(&buf).map_err(|_| ())?;
        }
        read_count = read_count + nread;
        if nread == 0 || Some(read_count) == count {
            break;
        }
    }

    println!("wrote {} bytes", read_count);

    Ok(())
}

// Reads the file at `src_path` and writes it to `dst_path`.
// The output format is detected based on the `dst_path` file extension.
fn convert(src_path: &str, dst_path: &str) -> std::result::Result<(), ()> {
    let src_file = match OpenOptions::new().read(true).open(src_path) {
        Ok(f) => f,
        Err(_) => {
            println!("Failed to open source file {}", src_path);
            return Err(());
        }
    };

    let dst_file = match OpenOptions::new()
        .read(true)
        .write(true)
        .create(true)
        .open(dst_path)
    {
        Ok(f) => f,
        Err(_) => {
            println!("Failed to open destination file {}", dst_path);
            return Err(());
        }
    };

    let dst_type = if dst_path.ends_with("qcow2") {
        qcow::ImageType::Qcow2
    } else {
        qcow::ImageType::Raw
    };

    match qcow::convert(src_file, dst_file, dst_type) {
        Ok(_) => {
            println!("Converted {} to {}", src_path, dst_path);
            Ok(())
        }
        Err(_) => {
            println!("Failed to copy from {} to {}", src_path, dst_path);
            Err(())
        }
    }
}