initial commit

This commit is contained in:
Bruce Leidl 2018-12-11 11:19:12 -05:00
commit 73b7878ca0
7 changed files with 902 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target
**/*.rs.bk

270
Cargo.lock generated Normal file
View 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
View 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
View 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
View 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
View 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
View 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
}
}