From 37826685143d7c140859ad986a6faa29cee533ec Mon Sep 17 00:00:00 2001 From: Bruce Leidl Date: Tue, 29 Jan 2019 11:50:10 -0500 Subject: [PATCH] Refactor how images are built to support realmfs images Main change in building images is that an empty 4096 byte block is prepended to raw image before compression so that upon decompression the header can be written without having to shuffle around decompressed image. --- citadel-image/src/build.rs | 65 +++++++++++++++++++++++++------------ citadel-image/src/config.rs | 13 ++++++-- 2 files changed, 54 insertions(+), 24 deletions(-) diff --git a/citadel-image/src/build.rs b/citadel-image/src/build.rs index 43d942e..b7a8272 100644 --- a/citadel-image/src/build.rs +++ b/citadel-image/src/build.rs @@ -7,6 +7,7 @@ use failure::ResultExt; use libcitadel::{Result,ImageHeader,verity,util,devkeys}; use crate::BuildConfig; +use std::path::Path; pub struct UpdateBuilder { config: BuildConfig, @@ -28,8 +29,7 @@ fn align(sz: usize, n: usize) -> usize { impl UpdateBuilder { pub fn new(config: BuildConfig) -> UpdateBuilder { - let filename = UpdateBuilder::target_filename(&config); - let image_data= config.workdir_path(&filename); + let image_data = config.workdir_path(UpdateBuilder::build_filename(&config)); UpdateBuilder { config, image_data, nblocks: None, shasum: None, verity_salt: None, @@ -37,8 +37,16 @@ impl UpdateBuilder { } } - fn target_filename(config: &BuildConfig) -> String { - format!("citadel-{}-{}-{:03}", config.img_name(), config.channel(), config.version()) + fn target_filename(&self) -> String { + format!("citadel-{}-{}-{:03}.img", self.config.img_name(), self.config.channel(), self.config.version()) + } + + fn build_filename(config: &BuildConfig) -> String { + format!("citadel-{}-{}-{:03}", config.image_type(), config.channel(), config.version()) + } + + fn verity_filename(&self) -> String { + format!("verity-hash-{}-{:03}", self.config.image_type(), self.config.version()) } pub fn build(&mut self) -> Result<()> { @@ -52,6 +60,9 @@ impl UpdateBuilder { .context("failed generating dm-verity hash tree")?; self.calculate_shasum()?; + + self.prepend_empty_block()?; + self.compress_image()?; self.write_final_image() @@ -60,8 +71,12 @@ impl UpdateBuilder { Ok(()) } + fn image(&self) -> &Path { + &self.image_data + } + fn pad_image(&mut self) -> Result<()> { - let meta = self.image_data.metadata()?; + let meta = self.image().metadata()?; let len = meta.len() as usize; if len % 512 != 0 { bail!("Image file size is not a multiple of sector size (512 bytes)"); @@ -73,7 +88,7 @@ impl UpdateBuilder { let zeros = vec![0u8; padlen]; let mut file = OpenOptions::new() .append(true) - .open(&self.image_data)?; + .open(self.image())?; file.write_all(&zeros)?; } @@ -85,8 +100,8 @@ impl UpdateBuilder { } fn calculate_shasum(&mut self) -> Result<()> { - let output = util::exec_cmdline_with_output("sha256sum", format!("{}", self.image_data.display())) - .context(format!("failed to calculate sha256 on {}", self.image_data.display()))?; + let output = util::exec_cmdline_with_output("sha256sum", self.image().display().to_string()) + .context(format!("failed to calculate sha256 on {}", self.image().display()))?; let v: Vec<&str> = output.split_whitespace().collect(); let shasum = v[0].trim().to_owned(); info!("Sha256 of image data is {}", shasum); @@ -94,11 +109,19 @@ impl UpdateBuilder { Ok(()) } + fn prepend_empty_block(&mut self) -> Result<()> { + let tmpfile = self.image().with_extension("tmp"); + let args = format!("if={} of={} bs=4096 seek=1 conv=sparse", self.image().display(), tmpfile.display()); + util::exec_cmdline("/usr/bin/dd", args)?; + fs::rename(tmpfile, self.image())?; + 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 hashfile = self.config.workdir_path(self.verity_filename()); let outfile = self.config.workdir_path("verity-format.out"); - let verity = verity::generate_initial_hashtree(&self.image_data, &hashfile)?; + let verity = verity::generate_initial_hashtree(self.image(), &hashfile)?; fs::write(outfile, verity.output()) .context("failed to write veritysetup command output to a file")?; @@ -119,33 +142,30 @@ impl UpdateBuilder { self.verity_root = Some(root); Ok(()) - } fn compress_image(&self) -> Result<()> { if self.config.compress() { info!("Compressing image data"); - util::exec_cmdline("xz", format!("-T0 {}", self.image_data.display())) - .context(format!("failed to compress {}", self.image_data.display()))?; + util::exec_cmdline("xz", format!("-T0 {}", self.image().display())) + .context(format!("failed to compress {}", self.image().display()))?; // Rename back to original image_data filename - let xz_filename = UpdateBuilder::target_filename(&self.config) + ".xz"; - fs::rename(self.config.workdir_path(&xz_filename), &self.image_data)?; + fs::rename(self.image().with_extension("xz"), self.image())?; } Ok(()) } fn write_final_image(&self) -> Result<()> { let header = self.generate_header()?; - let filename = format!("{}.img", UpdateBuilder::target_filename(&self.config)); - let image_path = self.config.workdir_path(&filename); + let target = self.config.workdir_path(self.target_filename()); - let mut out = File::create(&image_path) - .context(format!("could not open output file {}", image_path.display()))?; + let mut out = File::create(&target) + .context(format!("could not open output file {}", target.display()))?; header.write_header(&out)?; - let mut data = File::open(&self.image_data) - .context(format!("could not open image data file {}", self.image_data.display()))?; + let mut data = File::open(&self.image()) + .context(format!("could not open image data file {}", self.image().display()))?; io::copy(&mut data, &mut out) .context("error copying image data to output file")?; Ok(()) @@ -186,6 +206,9 @@ impl UpdateBuilder { if let Some(kid) = self.config.kernel_id() { writeln!(v, "kernel-id = \"{}\"", kid)?; } + if let Some(name) = self.config.realmfs_name() { + writeln!(v, "realmfs-name = \"{}\"", name)?; + } writeln!(v, "channel = \"{}\"", self.config.channel())?; writeln!(v, "version = {}", self.config.version())?; writeln!(v, "timestamp = \"{}\"", self.config.timestamp())?; diff --git a/citadel-image/src/config.rs b/citadel-image/src/config.rs index 2b2d335..03700b0 100644 --- a/citadel-image/src/config.rs +++ b/citadel-image/src/config.rs @@ -21,6 +21,9 @@ pub struct BuildConfig { #[serde(rename = "kernel-id")] kernel_id: Option, + #[serde(rename = "realmfs-name")] + realmfs_name: Option, + #[serde(skip)] basedir: PathBuf, #[serde(skip)] @@ -62,7 +65,7 @@ impl BuildConfig { fn validate(&self) -> Result<()> { let itype = self.image_type.as_str(); - if itype != "extra" && itype != "rootfs" && itype != "kernel" { + if itype != "extra" && itype != "rootfs" && itype != "kernel" && itype != "realmfs" { bail!("Invalid image type '{}'", self.image_type); }; let src = Path::new(&self.source); @@ -85,8 +88,8 @@ impl BuildConfig { &self.src_path } - pub fn workdir_path(&self, filename: &str) -> PathBuf { - self.basedir.join(filename) + pub fn workdir_path>(&self, filename: P) -> PathBuf { + self.basedir.join(filename.as_ref()) } pub fn img_name(&self) -> &str { @@ -101,6 +104,10 @@ impl BuildConfig { self.kernel_id.as_ref().map(|s| s.as_str()) } + pub fn realmfs_name(&self) -> Option<&str> { + self.realmfs_name.as_ref().map(|s| s.as_str()) + } + pub fn version(&self) -> usize { self.version }