1
0
forked from brl/citadel-tools

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.
This commit is contained in:
Bruce Leidl 2019-01-29 11:50:10 -05:00
parent e4665f3f5c
commit 3782668514
2 changed files with 54 additions and 24 deletions

View File

@ -7,6 +7,7 @@ use failure::ResultExt;
use libcitadel::{Result,ImageHeader,verity,util,devkeys}; use libcitadel::{Result,ImageHeader,verity,util,devkeys};
use crate::BuildConfig; use crate::BuildConfig;
use std::path::Path;
pub struct UpdateBuilder { pub struct UpdateBuilder {
config: BuildConfig, config: BuildConfig,
@ -28,8 +29,7 @@ fn align(sz: usize, n: usize) -> usize {
impl UpdateBuilder { impl UpdateBuilder {
pub fn new(config: BuildConfig) -> UpdateBuilder { pub fn new(config: BuildConfig) -> UpdateBuilder {
let filename = UpdateBuilder::target_filename(&config); let image_data = config.workdir_path(UpdateBuilder::build_filename(&config));
let image_data= config.workdir_path(&filename);
UpdateBuilder { UpdateBuilder {
config, image_data, config, image_data,
nblocks: None, shasum: None, verity_salt: None, nblocks: None, shasum: None, verity_salt: None,
@ -37,8 +37,16 @@ impl UpdateBuilder {
} }
} }
fn target_filename(config: &BuildConfig) -> String { fn target_filename(&self) -> String {
format!("citadel-{}-{}-{:03}", config.img_name(), config.channel(), config.version()) 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<()> { pub fn build(&mut self) -> Result<()> {
@ -52,6 +60,9 @@ impl UpdateBuilder {
.context("failed generating dm-verity hash tree")?; .context("failed generating dm-verity hash tree")?;
self.calculate_shasum()?; self.calculate_shasum()?;
self.prepend_empty_block()?;
self.compress_image()?; self.compress_image()?;
self.write_final_image() self.write_final_image()
@ -60,8 +71,12 @@ impl UpdateBuilder {
Ok(()) Ok(())
} }
fn image(&self) -> &Path {
&self.image_data
}
fn pad_image(&mut self) -> Result<()> { fn pad_image(&mut self) -> Result<()> {
let meta = self.image_data.metadata()?; let meta = self.image().metadata()?;
let len = meta.len() as usize; let len = meta.len() as usize;
if len % 512 != 0 { if len % 512 != 0 {
bail!("Image file size is not a multiple of sector size (512 bytes)"); 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 zeros = vec![0u8; padlen];
let mut file = OpenOptions::new() let mut file = OpenOptions::new()
.append(true) .append(true)
.open(&self.image_data)?; .open(self.image())?;
file.write_all(&zeros)?; file.write_all(&zeros)?;
} }
@ -85,8 +100,8 @@ impl UpdateBuilder {
} }
fn calculate_shasum(&mut self) -> Result<()> { fn calculate_shasum(&mut self) -> Result<()> {
let output = util::exec_cmdline_with_output("sha256sum", format!("{}", 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_data.display()))?; .context(format!("failed to calculate sha256 on {}", self.image().display()))?;
let v: Vec<&str> = output.split_whitespace().collect(); let v: Vec<&str> = output.split_whitespace().collect();
let shasum = v[0].trim().to_owned(); let shasum = v[0].trim().to_owned();
info!("Sha256 of image data is {}", shasum); info!("Sha256 of image data is {}", shasum);
@ -94,11 +109,19 @@ impl UpdateBuilder {
Ok(()) 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<()> { 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 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()) fs::write(outfile, verity.output())
.context("failed to write veritysetup command output to a file")?; .context("failed to write veritysetup command output to a file")?;
@ -119,33 +142,30 @@ impl UpdateBuilder {
self.verity_root = Some(root); self.verity_root = Some(root);
Ok(()) Ok(())
} }
fn compress_image(&self) -> Result<()> { fn compress_image(&self) -> Result<()> {
if self.config.compress() { if self.config.compress() {
info!("Compressing image data"); info!("Compressing image data");
util::exec_cmdline("xz", format!("-T0 {}", self.image_data.display())) util::exec_cmdline("xz", format!("-T0 {}", self.image().display()))
.context(format!("failed to compress {}", self.image_data.display()))?; .context(format!("failed to compress {}", self.image().display()))?;
// Rename back to original image_data filename // Rename back to original image_data filename
let xz_filename = UpdateBuilder::target_filename(&self.config) + ".xz"; fs::rename(self.image().with_extension("xz"), self.image())?;
fs::rename(self.config.workdir_path(&xz_filename), &self.image_data)?;
} }
Ok(()) Ok(())
} }
fn write_final_image(&self) -> Result<()> { fn write_final_image(&self) -> Result<()> {
let header = self.generate_header()?; let header = self.generate_header()?;
let filename = format!("{}.img", UpdateBuilder::target_filename(&self.config)); let target = self.config.workdir_path(self.target_filename());
let image_path = self.config.workdir_path(&filename);
let mut out = File::create(&image_path) let mut out = File::create(&target)
.context(format!("could not open output file {}", image_path.display()))?; .context(format!("could not open output file {}", target.display()))?;
header.write_header(&out)?; header.write_header(&out)?;
let mut data = File::open(&self.image_data) let mut data = File::open(&self.image())
.context(format!("could not open image data file {}", self.image_data.display()))?; .context(format!("could not open image data file {}", self.image().display()))?;
io::copy(&mut data, &mut out) io::copy(&mut data, &mut out)
.context("error copying image data to output file")?; .context("error copying image data to output file")?;
Ok(()) Ok(())
@ -186,6 +206,9 @@ impl UpdateBuilder {
if let Some(kid) = self.config.kernel_id() { if let Some(kid) = self.config.kernel_id() {
writeln!(v, "kernel-id = \"{}\"", kid)?; 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, "channel = \"{}\"", self.config.channel())?;
writeln!(v, "version = {}", self.config.version())?; writeln!(v, "version = {}", self.config.version())?;
writeln!(v, "timestamp = \"{}\"", self.config.timestamp())?; writeln!(v, "timestamp = \"{}\"", self.config.timestamp())?;

View File

@ -21,6 +21,9 @@ pub struct BuildConfig {
#[serde(rename = "kernel-id")] #[serde(rename = "kernel-id")]
kernel_id: Option<String>, kernel_id: Option<String>,
#[serde(rename = "realmfs-name")]
realmfs_name: Option<String>,
#[serde(skip)] #[serde(skip)]
basedir: PathBuf, basedir: PathBuf,
#[serde(skip)] #[serde(skip)]
@ -62,7 +65,7 @@ impl BuildConfig {
fn validate(&self) -> Result<()> { fn validate(&self) -> Result<()> {
let itype = self.image_type.as_str(); 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); bail!("Invalid image type '{}'", self.image_type);
}; };
let src = Path::new(&self.source); let src = Path::new(&self.source);
@ -85,8 +88,8 @@ impl BuildConfig {
&self.src_path &self.src_path
} }
pub fn workdir_path(&self, filename: &str) -> PathBuf { pub fn workdir_path<P: AsRef<Path>>(&self, filename: P) -> PathBuf {
self.basedir.join(filename) self.basedir.join(filename.as_ref())
} }
pub fn img_name(&self) -> &str { pub fn img_name(&self) -> &str {
@ -101,6 +104,10 @@ impl BuildConfig {
self.kernel_id.as_ref().map(|s| s.as_str()) 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 { pub fn version(&self) -> usize {
self.version self.version
} }