summary refs log tree commit diff
path: root/qcow_utils
diff options
context:
space:
mode:
authorDylan Reid <dgreid@chromium.org>2018-09-04 17:26:37 -0700
committerchrome-bot <chrome-bot@chromium.org>2018-09-19 15:40:06 -0700
commitcd9f86b2994a20347e1855e112369e5052f5a8cb (patch)
tree3a146777eefa5dcfa49d54928b2dfeec16274070 /qcow_utils
parent39401ff269cf74e0eaedbd7d0bff510ed01e4782 (diff)
downloadcrosvm-cd9f86b2994a20347e1855e112369e5052f5a8cb.tar
crosvm-cd9f86b2994a20347e1855e112369e5052f5a8cb.tar.gz
crosvm-cd9f86b2994a20347e1855e112369e5052f5a8cb.tar.bz2
crosvm-cd9f86b2994a20347e1855e112369e5052f5a8cb.tar.lz
crosvm-cd9f86b2994a20347e1855e112369e5052f5a8cb.tar.xz
crosvm-cd9f86b2994a20347e1855e112369e5052f5a8cb.tar.zst
crosvm-cd9f86b2994a20347e1855e112369e5052f5a8cb.zip
qcow: Add a utility program for qcow analysis
This program makes figuring out the state of a qcow file easier.

Change-Id: If297eb0cd835a86d8f284d3aef3d7e962e095726
Signed-off-by: Dylan Reid <dgreid@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/1207455
Reviewed-by: Daniel Verkamp <dverkamp@chromium.org>
Diffstat (limited to 'qcow_utils')
-rw-r--r--qcow_utils/Cargo.toml6
-rw-r--r--qcow_utils/src/qcow_img.rs272
2 files changed, 278 insertions, 0 deletions
diff --git a/qcow_utils/Cargo.toml b/qcow_utils/Cargo.toml
index 5ec3343..ca7e860 100644
--- a/qcow_utils/Cargo.toml
+++ b/qcow_utils/Cargo.toml
@@ -7,6 +7,12 @@ authors = ["The Chromium OS Authors"]
 path = "src/qcow_utils.rs"
 crate-type = ["cdylib"]
 
+[[bin]]
+name = "qcow_img"
+path = "src/qcow_img.rs"
+
 [dependencies]
+getopts = "*"
 libc = "*"
 qcow = { path = "../qcow" }
+sys_util = { path = "../sys_util" }
diff --git a/qcow_utils/src/qcow_img.rs b/qcow_utils/src/qcow_img.rs
new file mode 100644
index 0000000..1040de2
--- /dev/null
+++ b/qcow_utils/src/qcow_img.rs
@@ -0,0 +1,272 @@
+// 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
+    );
+}
+
+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)
+        }
+        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(())
+}