initial commit
This commit is contained in:
commit
73b7878ca0
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/target
|
||||
**/*.rs.bk
|
270
Cargo.lock
generated
Normal file
270
Cargo.lock
generated
Normal file
@ -0,0 +1,270 @@
|
||||
[[package]]
|
||||
name = "ansi_term"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"backtrace-sys 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-demangle 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backtrace-sys"
|
||||
version = "0.1.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "citadel-mkimage"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"failure 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.82 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.82 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"toml 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "2.32.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "failure"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"backtrace 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"failure_derive 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "failure_derive"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 0.15.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "0.4.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "0.6.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.1.43"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "redox_termios"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"redox_syscall 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.82"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.82"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 0.15.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "0.15.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "synstructure"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 0.15.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termion"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"redox_syscall 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.4.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"serde 1.0.82 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "vec_map"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[metadata]
|
||||
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
|
||||
"checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652"
|
||||
"checksum backtrace 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "89a47830402e9981c5c41223151efcced65a0510c13097c769cede7efb34782a"
|
||||
"checksum backtrace-sys 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)" = "c66d56ac8dabd07f6aacdaf633f4b8262f5b3601a810a0dcddffd5c22c69daa0"
|
||||
"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12"
|
||||
"checksum cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "f159dfd43363c4d08055a07703eb7a3406b0dac4d0584d96965a3262db3c9d16"
|
||||
"checksum cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "082bb9b28e00d3c9d39cc03e64ce4cea0f1bb9b3fde493f0cbc008472d22bdf4"
|
||||
"checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e"
|
||||
"checksum failure 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6dd377bcc1b1b7ce911967e3ec24fa19c3224394ec05b54aa7b083d498341ac7"
|
||||
"checksum failure_derive 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "64c2d913fe8ed3b6c6518eedf4538255b989945c14c2a7d5cbff62a5e2120596"
|
||||
"checksum libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)" = "2d2857ec59fadc0773853c664d2d18e7198e83883e7060b63c924cb077bd5c74"
|
||||
"checksum proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)" = "77619697826f31a02ae974457af0b29b723e5619e113e9397b8b82c6bd253f09"
|
||||
"checksum quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "53fa22a1994bd0f9372d7a816207d8a2677ad0325b073f5c5332760f0fb62b5c"
|
||||
"checksum redox_syscall 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "679da7508e9a6390aeaf7fbd02a800fdc64b73fe2204dd2c8ae66d22d9d5ad5d"
|
||||
"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
|
||||
"checksum rustc-demangle 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "bcfe5b13211b4d78e5c2cadfebd7769197d95c639c35a50057eb4c05de811395"
|
||||
"checksum serde 1.0.82 (registry+https://github.com/rust-lang/crates.io-index)" = "6fa52f19aee12441d5ad11c9a00459122bd8f98707cadf9778c540674f1935b6"
|
||||
"checksum serde_derive 1.0.82 (registry+https://github.com/rust-lang/crates.io-index)" = "96a7f9496ac65a2db5929afa087b54f8fc5008dcfbe48a8874ed20049b0d6154"
|
||||
"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550"
|
||||
"checksum syn 0.15.22 (registry+https://github.com/rust-lang/crates.io-index)" = "ae8b29eb5210bc5cf63ed6149cbf9adfc82ac0be023d8735c176ee74a2db4da7"
|
||||
"checksum synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73687139bf99285483c96ac0add482c3776528beac1d97d444f6e91f203a2015"
|
||||
"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096"
|
||||
"checksum textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "307686869c93e71f94da64286f9a9524c0f308a9e1c87a583de8e9c9039ad3f6"
|
||||
"checksum toml 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "758664fc71a3a69038656bee8b6be6477d2a6c315a6b81f7081f591bffa4111f"
|
||||
"checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526"
|
||||
"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
|
||||
"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
|
||||
"checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0"
|
||||
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
12
Cargo.toml
Normal file
12
Cargo.toml
Normal file
@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "citadel-mkimage"
|
||||
version = "0.1.0"
|
||||
authors = ["Bruce Leidl <bruce@subgraph.com>"]
|
||||
homepage = "https://github.com/subgraph/citadel"
|
||||
|
||||
[dependencies]
|
||||
clap = "2.32.0"
|
||||
failure = "0.1.3"
|
||||
serde_derive = "1.0.82"
|
||||
serde = "1.0.82"
|
||||
toml = "0.4.10"
|
227
src/build.rs
Normal file
227
src/build.rs
Normal file
@ -0,0 +1,227 @@
|
||||
use std::path::PathBuf;
|
||||
use std::fs::OpenOptions;
|
||||
use std::path::Path;
|
||||
use std::fs::{self,File};
|
||||
use std::io::{self,Write};
|
||||
|
||||
use failure::ResultExt;
|
||||
|
||||
use Result;
|
||||
use BuildConfig;
|
||||
use util;
|
||||
|
||||
pub struct UpdateBuilder {
|
||||
config: BuildConfig,
|
||||
|
||||
nblocks: Option<usize>,
|
||||
shasum: Option<String>,
|
||||
verity_salt: Option<String>,
|
||||
verity_root: Option<String>,
|
||||
}
|
||||
|
||||
|
||||
const BLOCK_SIZE: usize = 4096;
|
||||
fn align(sz: usize, n: usize) -> usize {
|
||||
(sz + (n - 1)) & !(n - 1)
|
||||
}
|
||||
|
||||
|
||||
impl UpdateBuilder {
|
||||
|
||||
pub fn new(config: BuildConfig) -> Result<UpdateBuilder> {
|
||||
let builder = UpdateBuilder {
|
||||
config,
|
||||
nblocks: None, shasum: None, verity_salt: None,
|
||||
verity_root: None,
|
||||
};
|
||||
|
||||
builder.copy_source_image()?;
|
||||
Ok(builder)
|
||||
}
|
||||
|
||||
pub fn build(&mut self) -> Result<()> {
|
||||
self.pad_image()
|
||||
.context("failed writing padding to image")?;
|
||||
|
||||
self.generate_verity()
|
||||
.context("failed generating dm-verity hash tree")?;
|
||||
|
||||
self.calculate_shasum()?;
|
||||
self.compress_image()?;
|
||||
|
||||
self.write_final_image()
|
||||
.context("failed to write final image file")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn target(&self, ext: Option<&str>) -> PathBuf {
|
||||
match ext {
|
||||
Some(s) => self.config.workdir_path(&format!("citadel-{}-{}-{:03}.{}", self.config.img_name(), self.config.channel(), self.config.version(), s)),
|
||||
None => self.config.workdir_path(&format!("citadel-{}-{}-{:03}", self.config.img_name(), self.config.channel(), self.config.version()))
|
||||
}
|
||||
}
|
||||
|
||||
fn copy_source_image(&self) -> Result<()> {
|
||||
sanity_check_source(self.config.source())?;
|
||||
let target = self.target(None);
|
||||
info!("Copying source file to {}", target.display());
|
||||
let mut from = File::open(self.config.source())?;
|
||||
let mut to = File::create(&target)?;
|
||||
io::copy(&mut from, &mut to)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn pad_image(&mut self) -> Result<()> {
|
||||
let target = self.target(None);
|
||||
let meta = target.metadata()?;
|
||||
let len = meta.len() as usize;
|
||||
let padlen = align(len, BLOCK_SIZE) - len;
|
||||
|
||||
if padlen > 0 {
|
||||
info!("Padding image with {} zero bytes to 4096 byte block boundary", padlen);
|
||||
let zeros = vec![0u8; padlen];
|
||||
let mut file = OpenOptions::new()
|
||||
.append(true)
|
||||
.open(&target)?;
|
||||
file.write_all(&zeros)?;
|
||||
}
|
||||
|
||||
let nblocks = (len + padlen) / 4096;
|
||||
info!("Image contains {} blocks of data", nblocks);
|
||||
self.nblocks = Some(nblocks);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn calculate_shasum(&mut self) -> Result<()> {
|
||||
let shasum = util::sha256(self.target(None))?;
|
||||
info!("Sha256 of image data is {}", shasum);
|
||||
self.shasum = Some(shasum);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn generate_verity(&mut self) -> Result<()> {
|
||||
let hashfile = self.config.workdir_path(&format!("verity-hash-{}-{:03}", self.config.image_type(), self.config.version()));
|
||||
let outfile = self.config.workdir_path("verity-format.out");
|
||||
|
||||
let verity = util::verity_initial_hashtree(self.target(None), &hashfile)?;
|
||||
|
||||
|
||||
fs::write(outfile, verity.output())
|
||||
.context("failed to write veritysetup command output to a file")?;
|
||||
|
||||
let root = match verity.root_hash() {
|
||||
Some(s) => s.to_owned(),
|
||||
None => bail!("no root hash found in verity format output"),
|
||||
};
|
||||
|
||||
let salt = match verity.salt() {
|
||||
Some(s) => s.to_owned(),
|
||||
None => bail!("no verity salt found in verity format output"),
|
||||
};
|
||||
|
||||
info!("Verity hash tree calculated, verity-root = {}", root);
|
||||
|
||||
self.verity_salt = Some(salt);
|
||||
self.verity_root = Some(root);
|
||||
|
||||
Ok(())
|
||||
|
||||
}
|
||||
|
||||
fn compress_image(&self) -> Result<()> {
|
||||
info!("Compressing image data");
|
||||
util::xz_compress(self.target(None))
|
||||
}
|
||||
|
||||
fn write_final_image(&self) -> Result<()> {
|
||||
let header = self.generate_header()?;
|
||||
let mut path = self.target(None);
|
||||
let fname = path.file_name().unwrap().to_str().unwrap().to_owned();
|
||||
path.set_file_name(fname + ".img");
|
||||
|
||||
let mut out = File::create(&path)
|
||||
.context(format!("could not open output file {}", path.display()))?;
|
||||
|
||||
out.write_all(&header)?;
|
||||
|
||||
path.set_extension("xz");
|
||||
let mut data = File::open(&path)
|
||||
.context(format!("could not open compressed image data file {}", path.display()))?;
|
||||
io::copy(&mut data, &mut out)
|
||||
.context("error copying image data to output file")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
///
|
||||
/// The Image Header structure is stored in a 4096 byte block at the start of
|
||||
/// every resource image file. When an image is installed to a partition it
|
||||
/// is stored at the last 4096 byte block of the block device for the partition.
|
||||
///
|
||||
/// The layout of this structure is the following:
|
||||
///
|
||||
/// field size (bytes) offset
|
||||
/// ----- ------------ ------
|
||||
///
|
||||
/// magic 4 0
|
||||
/// status 1 4
|
||||
/// flags 1 5
|
||||
/// length 2 6
|
||||
///
|
||||
/// metainfo <length> 8
|
||||
///
|
||||
/// signature 64 8 + length
|
||||
///
|
||||
fn generate_header(&self) -> Result<Vec<u8>> {
|
||||
let mut hdr = vec![0u8; BLOCK_SIZE];
|
||||
hdr[0..4].copy_from_slice(b"SGOS");
|
||||
hdr[5] = 0x04; // FLAG_DATA_COMPRESSED
|
||||
|
||||
let metainfo = self.generate_metainfo();
|
||||
let metalen = metainfo.len();
|
||||
hdr[6] = (metalen >> 8) as u8;
|
||||
hdr[7] = metalen as u8;
|
||||
hdr[8..8+metalen].copy_from_slice(&metainfo);
|
||||
|
||||
Ok(hdr)
|
||||
}
|
||||
|
||||
fn generate_metainfo(&self) -> Vec<u8> {
|
||||
// writes to Vec can't fail, unwrap once to avoid clutter
|
||||
self._generate_metainfo().unwrap()
|
||||
}
|
||||
|
||||
fn _generate_metainfo(&self) -> Result<Vec<u8>> {
|
||||
assert!(self.verity_salt.is_some() && self.verity_root.is_some(),
|
||||
"no verity-salt/verity-root in generate_metainfo()");
|
||||
|
||||
let mut v = Vec::new();
|
||||
writeln!(v, "image-type = \"{}\"", self.config.image_type())?;
|
||||
writeln!(v, "channel = \"{}\"", self.config.channel())?;
|
||||
writeln!(v, "version = {}", self.config.version())?;
|
||||
writeln!(v, "nblocks = {}", self.nblocks.unwrap())?;
|
||||
writeln!(v, "shasum = \"{}\"", self.shasum.as_ref().unwrap())?;
|
||||
writeln!(v, "verity-salt = \"{}\"", self.verity_salt.as_ref().unwrap())?;
|
||||
writeln!(v, "verity-root = \"{}\"", self.verity_root.as_ref().unwrap())?;
|
||||
Ok(v)
|
||||
}
|
||||
}
|
||||
|
||||
fn sanity_check_source<P: AsRef<Path>>(src: P) -> Result<()> {
|
||||
let src: &Path = src.as_ref();
|
||||
let meta = match src.metadata() {
|
||||
Ok(md) => md,
|
||||
Err(e) => bail!("Could not load image file {}: {}", src.display(), e),
|
||||
};
|
||||
|
||||
if !meta.file_type().is_file() {
|
||||
bail!("Image file {} exists but is not a regular file", src.display());
|
||||
}
|
||||
|
||||
if meta.len() % 512 != 0 {
|
||||
bail!("Image file size is not a multiple of sector size (512 bytes)");
|
||||
}
|
||||
Ok(())
|
||||
}
|
97
src/config.rs
Normal file
97
src/config.rs
Normal file
@ -0,0 +1,97 @@
|
||||
|
||||
use std::path::{Path,PathBuf};
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
|
||||
use Result;
|
||||
use toml;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct BuildConfig {
|
||||
#[serde (rename="image-type")]
|
||||
image_type: String,
|
||||
channel: String,
|
||||
version: usize,
|
||||
source: String,
|
||||
#[serde (rename="kernel-version")]
|
||||
kernel_version: Option<String>,
|
||||
|
||||
#[serde(skip)]
|
||||
basedir: PathBuf,
|
||||
#[serde(skip)]
|
||||
src_path: PathBuf,
|
||||
#[serde(skip)]
|
||||
img_name: String,
|
||||
}
|
||||
|
||||
impl BuildConfig {
|
||||
pub fn load<P: AsRef<Path>>(path: P) -> Result<BuildConfig> {
|
||||
let mut path = path.as_ref().to_owned();
|
||||
if path.is_dir() {
|
||||
path.push("mkimage.conf");
|
||||
}
|
||||
|
||||
let mut config = match BuildConfig::from_path(&path) {
|
||||
Ok(config) => config,
|
||||
Err(e) => bail!("Failed to load config file {}: {}", path.display(), e),
|
||||
};
|
||||
|
||||
path.pop();
|
||||
config.basedir = path;
|
||||
config.src_path = PathBuf::from(&config.source);
|
||||
config.img_name = match config.kernel_version {
|
||||
Some(ref version) => format!("{}-{}", &config.image_type, version),
|
||||
None => config.image_type.to_owned(),
|
||||
};
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
fn from_path(path: &Path) -> Result<BuildConfig> {
|
||||
let mut f = File::open(path)?;
|
||||
let mut s = String::new();
|
||||
f.read_to_string(&mut s)?;
|
||||
let config = toml::from_str::<BuildConfig>(&s)?;
|
||||
config.validate()?;
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
fn validate(&self) -> Result<()> {
|
||||
let itype = self.image_type.as_str();
|
||||
if itype != "extra" && itype != "rootfs" && itype != "modules" {
|
||||
bail!("Invalid image type '{}'", self.image_type);
|
||||
};
|
||||
let src = Path::new(&self.source);
|
||||
if !src.is_file() {
|
||||
bail!("Source path '{}' does not exist or is not a regular file");
|
||||
}
|
||||
if self.image_type == "modules" && self.kernel_version.is_none() {
|
||||
bail!("Cannot build 'modules' image without kernel-version field");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn source(&self) -> &Path {
|
||||
&self.src_path
|
||||
}
|
||||
|
||||
pub fn workdir_path(&self, filename: &str) -> PathBuf {
|
||||
self.basedir.join(filename)
|
||||
}
|
||||
|
||||
pub fn img_name(&self) -> &str {
|
||||
&self.img_name
|
||||
}
|
||||
|
||||
pub fn version(&self) -> usize {
|
||||
self.version
|
||||
}
|
||||
|
||||
pub fn channel(&self) -> &str {
|
||||
&self.channel
|
||||
}
|
||||
|
||||
pub fn image_type(&self) -> &str {
|
||||
&self.image_type
|
||||
}
|
||||
}
|
182
src/main.rs
Normal file
182
src/main.rs
Normal file
@ -0,0 +1,182 @@
|
||||
#[macro_use] extern crate failure;
|
||||
#[macro_use] extern crate serde_derive;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! info {
|
||||
($e:expr) => { if $crate::verbose() { println!("[+] {}", $e);} };
|
||||
($fmt:expr, $($arg:tt)+) => { if $crate::verbose() { println!("[+] {}", format!($fmt, $($arg)+));} };
|
||||
}
|
||||
#[macro_export]
|
||||
macro_rules! warn {
|
||||
($e:expr) => { println!("WARNING: {}", $e); };
|
||||
($fmt:expr, $($arg:tt)+) => { println!("WARNING: {}", format!($fmt, $($arg)+)); };
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! notify {
|
||||
($e:expr) => { println!("[+] {}", $e); };
|
||||
($fmt:expr, $($arg:tt)+) => { println!("[+] {}", format!($fmt, $($arg)+)); };
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
pub static VERBOSE: RefCell<bool> = RefCell::new(false);
|
||||
}
|
||||
|
||||
pub fn verbose() -> bool {
|
||||
VERBOSE.with(|f| {
|
||||
*f.borrow()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_verbose(val: bool) {
|
||||
VERBOSE.with(|f| {
|
||||
*f.borrow_mut() = val
|
||||
});
|
||||
}
|
||||
|
||||
extern crate clap;
|
||||
extern crate toml;
|
||||
|
||||
use std::process::exit;
|
||||
use std::result;
|
||||
use std::cell::RefCell;
|
||||
|
||||
use clap::{App,Arg,SubCommand,ArgMatches};
|
||||
use clap::AppSettings::*;
|
||||
use failure::Error;
|
||||
|
||||
pub type Result<T> = result::Result<T,Error>;
|
||||
use build::UpdateBuilder;
|
||||
use config::BuildConfig;
|
||||
|
||||
mod build;
|
||||
mod config;
|
||||
mod util;
|
||||
|
||||
|
||||
|
||||
fn main() {
|
||||
let app = App::new("citadel-mkimage")
|
||||
.about("Citadel update image builder")
|
||||
.settings(&[ArgRequiredElseHelp,ColoredHelp, DisableHelpSubcommand, DisableVersion, DeriveDisplayOrder])
|
||||
|
||||
.subcommand(SubCommand::with_name("build")
|
||||
.about("Build an update image specified by a configuration file")
|
||||
.arg(Arg::with_name("build-file")
|
||||
.required(true)
|
||||
.help("Path to image build config file")));
|
||||
|
||||
set_verbose(true);
|
||||
let matches = app.get_matches();
|
||||
|
||||
let result = match matches.subcommand() {
|
||||
("build", Some(m)) => build_image(m),
|
||||
_ => Ok(()),
|
||||
};
|
||||
|
||||
if let Err(ref e) = result {
|
||||
println!("Error: {}", format_error(e));
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
fn format_error(err: &Error) -> String {
|
||||
let mut output = err.to_string();
|
||||
let mut prev = err.as_fail();
|
||||
while let Some(next) = prev.cause() {
|
||||
output.push_str(": ");
|
||||
output.push_str(&next.to_string());
|
||||
prev = next;
|
||||
}
|
||||
output
|
||||
}
|
||||
|
||||
/*
|
||||
fn find_source_image(update_type: &str, filename: &str) -> Result<PathBuf> {
|
||||
let mut path = PathBuf::from(filename);
|
||||
let meta = path.metadata()
|
||||
.context(format!("could not find source file or directory {}", filename))?;
|
||||
|
||||
if meta.file_type().is_file() {
|
||||
return Ok(path);
|
||||
}
|
||||
if !meta.file_type().is_dir() {
|
||||
bail!("source not found: {}", filename);
|
||||
}
|
||||
|
||||
path.push(format!("build/images/citadel-{}-image-intel-corei7-64.ext2", update_type));
|
||||
let meta = path.metadata()
|
||||
.context(format!("could not find source file {}", path.display()))?;
|
||||
|
||||
if !meta.file_type().is_file() {
|
||||
bail!("source {} exists but is not a file", path.display());
|
||||
}
|
||||
|
||||
let canonical = fs::canonicalize(&path)
|
||||
.context(format!("failed to resolve {} to absolute path", path.display()))?;
|
||||
Ok(canonical)
|
||||
}
|
||||
|
||||
fn parse_version(arg_matches: &ArgMatches) -> Result<usize> {
|
||||
let v = arg_matches.value_of("version")
|
||||
.unwrap_or("0")
|
||||
.parse::<usize>()
|
||||
.context("Unable to parse version argument")?;
|
||||
Ok(v)
|
||||
}
|
||||
*/
|
||||
|
||||
fn build_image(arg_matches: &ArgMatches) -> Result<()> {
|
||||
let build_file = arg_matches.value_of("build-file").unwrap();
|
||||
let config = BuildConfig::load(build_file)?;
|
||||
let mut builder = UpdateBuilder::new(config)?;
|
||||
builder.build()?;
|
||||
Ok(())
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
fn build_update(update_type: &str, arg_matches: &ArgMatches) -> Result<()> {
|
||||
let config = Config::load("/usr/share/citadel/citadel-image.conf")?;
|
||||
let channel = config.get_default_channel().unwrap(); // XXX unwrap()
|
||||
|
||||
let source_name = arg_matches.value_of("citadel-base").unwrap();
|
||||
let source = find_source_image(update_type, source_name)?;
|
||||
let version = parse_version(arg_matches)?;
|
||||
|
||||
info!("Building file {} as {} update image for channel {} with version {}", source.display(), update_type, channel.name(), version);
|
||||
let conf = BuildConfig::load("").unwrap();
|
||||
let mut builder = UpdateBuilder::new(conf)?;
|
||||
builder.build()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn rootfs_update(arg_matches: &ArgMatches) -> Result<()> {
|
||||
build_update("rootfs", arg_matches)
|
||||
}
|
||||
|
||||
fn modules_update(arg_matches: &ArgMatches) -> Result<()> {
|
||||
build_update("modules", arg_matches)
|
||||
}
|
||||
|
||||
fn extra_update(arg_matches: &ArgMatches) -> Result<()> {
|
||||
build_update("extra", arg_matches)
|
||||
}
|
||||
|
||||
fn generate_verity(_arg_matches: &ArgMatches) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn generate_keys() -> Result<()> {
|
||||
let keys = SigningKeys::generate()?;
|
||||
println!("pubkey = \"{}\"", keys.to_public_hex());
|
||||
println!("keypair = \"{}\"", keys.to_hex());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn image_info(arg_matches: &ArgMatches) -> Result<()> {
|
||||
let image = arg_matches.value_of("image-file").unwrap();
|
||||
let config = Config::load("/usr/share/citadel/citadel-image.conf")?;
|
||||
info::show_info(image, &config)
|
||||
}
|
||||
*/
|
112
src/util.rs
Normal file
112
src/util.rs
Normal file
@ -0,0 +1,112 @@
|
||||
|
||||
use std::process::{Command,Stdio};
|
||||
use std::path::Path;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use failure::ResultExt;
|
||||
|
||||
use Result;
|
||||
|
||||
|
||||
pub fn sha256<P: AsRef<Path>>(path: P) -> Result<String> {
|
||||
let output = exec_command_with_output("/usr/bin/sha256sum", &[pathstr(path.as_ref())])
|
||||
.context(format!("failed to calculate sha256 on {}", path.as_ref().display()))?;
|
||||
|
||||
let v: Vec<&str> = output.split_whitespace().collect();
|
||||
Ok(v[0].trim().to_owned())
|
||||
}
|
||||
|
||||
pub fn xz_compress<P: AsRef<Path>>(path: P) -> Result<()> {
|
||||
exec_command("/usr/bin/xz", &["-T0", pathstr(path.as_ref())])
|
||||
.context(format!("failed to compress {}", path.as_ref().display()))?;
|
||||
Ok( ())
|
||||
}
|
||||
|
||||
pub fn verity_initial_hashtree<P: AsRef<Path>, Q: AsRef<Path>>(path: P, hashfile: Q) -> Result<VerityOutput> {
|
||||
let output = exec_command_with_output("/usr/sbin/veritysetup",
|
||||
&["format",pathstr(path.as_ref()), pathstr(hashfile.as_ref())])
|
||||
.context("veritysetup format command failed")?;
|
||||
|
||||
Ok(VerityOutput::parse(&output))
|
||||
}
|
||||
|
||||
fn pathstr(path: &Path) -> &str {
|
||||
path.to_str().unwrap()
|
||||
}
|
||||
|
||||
fn exec_command(cmd_path: &str, args: &[&str]) -> Result<()> {
|
||||
let status = Command::new(cmd_path)
|
||||
.args(args)
|
||||
.stderr(Stdio::inherit())
|
||||
.status()
|
||||
.context(format!("unable to execute {}", cmd_path))?;
|
||||
|
||||
if !status.success() {
|
||||
match status.code() {
|
||||
Some(code) => bail!("command {} failed with exit code: {}", cmd_path, code),
|
||||
None => bail!("command {} failed with no exit code", cmd_path),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn exec_command_with_output(cmd_path: &str, args: &[&str]) -> Result<String> {
|
||||
let res = Command::new(cmd_path)
|
||||
.args(args)
|
||||
.stderr(Stdio::inherit())
|
||||
.output()
|
||||
.context(format!("unable to execute {}", cmd_path))?;
|
||||
|
||||
if !res.status.success() {
|
||||
match res.status.code() {
|
||||
Some(code) => bail!("command {} failed with exit code: {}", cmd_path, code),
|
||||
None => bail!("command {} failed with no exit code", cmd_path),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(String::from_utf8(res.stdout).unwrap().trim().to_owned())
|
||||
}
|
||||
|
||||
/// The output from the `veritysetup format` command can be parsed as key/value
|
||||
/// pairs. This class parses the output and stores it in a map for querying.
|
||||
pub struct VerityOutput {
|
||||
output: String,
|
||||
map: HashMap<String,String>,
|
||||
}
|
||||
|
||||
impl VerityOutput {
|
||||
/// Parse the string `output` as standard output from the dm-verity
|
||||
/// `veritysetup format` command.
|
||||
fn parse(output: &str) -> VerityOutput {
|
||||
let mut vo = VerityOutput {
|
||||
output: output.to_owned(),
|
||||
map: HashMap::new(),
|
||||
};
|
||||
for line in output.lines() {
|
||||
vo.parse_line(line);
|
||||
}
|
||||
vo
|
||||
}
|
||||
|
||||
fn parse_line(&mut self, line: &str) {
|
||||
let v = line.split(':')
|
||||
.map(|s| s.trim())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if v.len() == 2 {
|
||||
self.map.insert(v[0].to_owned(), v[1].to_owned());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn root_hash(&self) -> Option<&str> {
|
||||
self.map.get("Root hash").map(|s| s.as_str())
|
||||
}
|
||||
|
||||
pub fn salt(&self) -> Option<&str> {
|
||||
self.map.get("Salt").map(|s| s.as_str())
|
||||
}
|
||||
|
||||
pub fn output(&self) -> &str {
|
||||
&self.output
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user