blob: 17066671c1442d3518d57b96f23b78b323055b86 [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/// A builder for the FVM that accepts a few attributes and optionally a list of filesystems.
///
/// ```
/// let slice_size: u64 = 1;
/// let include_account: bool = true;
/// let compress = true;
/// let builder = FvmBuilder::new(
/// fvm_tool,
/// "path/to/output.blk",
/// slice_size,
/// include_account,
/// compress,
/// Fvm::Standard {
/// resize_image_file_to_fit: false,
/// truncate_to_length: false,
/// },
/// );
/// builder.filesystem(Filesystem::BlobFS {
/// path: "path/to/blob.blk",
/// attributes: FilesystemAttributes {
/// name: "blob",
/// minimum_inodes: 100,
/// minimum_data_bytes: 200,
/// maximum_bytes: 200
/// },
/// });
/// builder.build();
/// ```
///
use anyhow::{Context, Result};
use assembly_tool::Tool;
use camino::{Utf8Path, Utf8PathBuf};
use serde::{Deserialize, Serialize};
/// The FVM builder.
pub struct FvmBuilder {
/// The fvm host tool.
tool: Box<dyn Tool>,
/// The path to write the FVM to.
output: Utf8PathBuf,
/// The size of a slice for the FVM.
slice_size: u64,
/// Whether to include an account in the FVM.
include_account: bool,
/// Whether to compress the FVM.
compress: bool,
/// The type of FVM to generate.
fvm_type: FvmType,
/// A list of filesystems to add to the FVM.
filesystems: Vec<Filesystem>,
}
/// The type of the FVM to generate and all information required to build that type.
pub enum FvmType {
/// A plain, non-sparse FVM.
Standard {
/// Shrink the FVM to fit exactly the contents.
resize_image_file_to_fit: bool,
/// After the optional resize, truncate the file to this length.
truncate_to_length: Option<u64>,
},
/// A sparse FVM that is typically used for paving.
Sparse {
/// The maximum disk size for the sparse FVM.
/// The build will fail if the sparse FVM is larger than this.
max_disk_size: Option<u64>,
},
}
/// A filesystem to add to the FVM.
#[derive(Clone)]
pub enum Filesystem {
/// A blobfs filesystem.
BlobFS {
/// The path to the filesystem block file on the host.
path: Utf8PathBuf,
/// The attributes of the filesystem to create.
attributes: FilesystemAttributes,
},
/// An empty data filesystem that will be formatted with fxfs/minfs when Fuchsia boots.
EmptyData,
/// Reserved slices for future use.
Reserved {
/// The number of slices to reserve.
slices: u64,
},
}
/// Attributes common to all filesystems.
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct FilesystemAttributes {
/// The name of the partition. Typically "blob" or "data".
pub name: String,
/// The minimum number of inodes to add to the filesystem.
pub minimum_inodes: Option<u64>,
/// The minimum number of data bytes to set for the filesystem.
pub minimum_data_bytes: Option<u64>,
/// The maximum number of bytes for the filesystem.
pub maximum_bytes: Option<u64>,
}
impl FvmBuilder {
/// Construct a new FvmBuilder.
pub fn new(
tool: Box<dyn Tool>,
output: impl AsRef<Utf8Path>,
slice_size: u64,
include_account: bool,
compress: bool,
fvm_type: FvmType,
) -> Self {
Self {
tool,
output: output.as_ref().to_path_buf(),
slice_size,
include_account,
compress,
fvm_type,
filesystems: Vec::<Filesystem>::new(),
}
}
/// Set the output path.
pub fn output(&mut self, output: impl AsRef<Utf8Path>) {
self.output = output.as_ref().to_path_buf();
}
/// Add a `filesystem` to the FVM.
pub fn filesystem(&mut self, filesystem: Filesystem) {
self.filesystems.push(filesystem);
}
/// Build the FVM.
pub fn build(&self) -> Result<()> {
let args = self.build_args().context("building fvm arguments")?;
self.tool.run(&args)
}
/// Build the arguments to pass to the fvm tool.
fn build_args(&self) -> Result<Vec<String>> {
let mut args: Vec<String> = Vec::new();
args.push(self.output.to_string());
// Append key and value to the `args` if the value is present.
fn maybe_append_value(
args: &mut Vec<String>,
key: impl AsRef<str>,
value: Option<impl std::string::ToString>,
) {
if let Some(value) = value {
args.push(format!("--{}", key.as_ref()));
args.push(value.to_string());
}
}
// Append the arguments that specify a filesystem to add to the FVM.
fn append_filesystem(
args: &mut Vec<String>,
path: impl AsRef<str>,
attributes: &FilesystemAttributes,
) {
maybe_append_value(args, &attributes.name, Some(path.as_ref()));
maybe_append_value(args, "minimum-inodes", attributes.minimum_inodes);
maybe_append_value(args, "minimum-data-bytes", attributes.minimum_data_bytes);
maybe_append_value(args, "maximum-bytes", attributes.maximum_bytes);
}
// Append the type-specific args.
match &self.fvm_type {
FvmType::Standard { resize_image_file_to_fit, truncate_to_length } => {
args.push("create".to_string());
if *resize_image_file_to_fit {
args.push("--resize-image-file-to-fit".to_string());
}
maybe_append_value(&mut args, "length", truncate_to_length.as_ref());
}
FvmType::Sparse { max_disk_size } => {
args.push("sparse".to_string());
maybe_append_value(&mut args, "max-disk-size", max_disk_size.as_ref());
}
}
// Append the common args.
maybe_append_value(&mut args, "slice", Some(self.slice_size.to_string()));
if self.compress {
maybe_append_value(&mut args, "compress", Some("lz4"));
}
// Append the filesystem args.
for fs in &self.filesystems {
match fs {
Filesystem::BlobFS { path, attributes } => {
append_filesystem(&mut args, path.to_string(), attributes);
}
Filesystem::EmptyData => {
args.push("--with-empty-data".to_string());
}
Filesystem::Reserved { slices } => {
args.push("--reserve-slices".to_string());
args.push(slices.to_string());
}
}
}
if self.include_account {
args.push("--with-empty-account-partition".to_string())
}
Ok(args)
}
}
#[cfg(test)]
mod tests {
use super::*;
use assembly_tool::testing::FakeToolProvider;
use assembly_tool::ToolProvider;
fn default_standard() -> FvmType {
FvmType::Standard { resize_image_file_to_fit: false, truncate_to_length: None }
}
#[test]
fn standard_args_no_filesystem() {
let tools = FakeToolProvider::default();
let fvm_tool = tools.get_tool("fvm").unwrap();
let builder = FvmBuilder::new(fvm_tool, "mypath", 1, false, false, default_standard());
let args = builder.build_args().unwrap();
assert_eq!(vec!["mypath", "create", "--slice", "1"], args);
}
#[test]
fn standard_args_with_filesystem() {
let tools = FakeToolProvider::default();
let fvm_tool = tools.get_tool("fvm").unwrap();
let mut builder = FvmBuilder::new(fvm_tool, "mypath", 1, false, false, default_standard());
builder.filesystem(Filesystem::BlobFS {
path: Utf8PathBuf::from("path/to/blob.blk"),
attributes: FilesystemAttributes {
name: "blob".to_string(),
minimum_inodes: Some(100),
minimum_data_bytes: Some(200),
maximum_bytes: Some(300),
},
});
let args = builder.build_args().unwrap();
assert_eq!(
vec![
"mypath",
"create",
"--slice",
"1",
"--blob",
"path/to/blob.blk",
"--minimum-inodes",
"100",
"--minimum-data-bytes",
"200",
"--maximum-bytes",
"300",
],
args
);
}
#[test]
fn standard_args_compressed() {
let tools = FakeToolProvider::default();
let fvm_tool = tools.get_tool("fvm").unwrap();
let builder = FvmBuilder::new(fvm_tool, "mypath", 1, false, true, default_standard());
let args = builder.build_args().unwrap();
assert_eq!(vec!["mypath", "create", "--slice", "1", "--compress", "lz4",], args);
}
#[test]
fn standard_args_with_empty_account() {
let tools = FakeToolProvider::default();
let fvm_tool = tools.get_tool("fvm").unwrap();
let builder = FvmBuilder::new(fvm_tool, "mypath", 1, true, false, default_standard());
let args = builder.build_args().unwrap();
assert_eq!(
vec!["mypath", "create", "--slice", "1", "--with-empty-account-partition",],
args
);
}
#[test]
fn standard_args_with_reserved() {
let tools = FakeToolProvider::default();
let fvm_tool = tools.get_tool("fvm").unwrap();
let mut builder = FvmBuilder::new(fvm_tool, "mypath", 1, false, false, default_standard());
builder.filesystem(Filesystem::Reserved { slices: 500 });
let args = builder.build_args().unwrap();
assert_eq!(vec!["mypath", "create", "--slice", "1", "--reserve-slices", "500",], args);
}
#[test]
fn standard_args_resize_and_truncate() {
let tools = FakeToolProvider::default();
let fvm_tool = tools.get_tool("fvm").unwrap();
let builder = FvmBuilder::new(
fvm_tool,
"mypath",
1,
false,
false,
FvmType::Standard { resize_image_file_to_fit: true, truncate_to_length: Some(500) },
);
let args = builder.build_args().unwrap();
assert_eq!(
vec![
"mypath",
"create",
"--resize-image-file-to-fit",
"--length",
"500",
"--slice",
"1",
],
args
);
}
#[test]
fn sparse_args_no_max_size() {
let tools = FakeToolProvider::default();
let fvm_tool = tools.get_tool("fvm").unwrap();
let builder = FvmBuilder::new(
fvm_tool,
"mypath",
1,
false,
false,
FvmType::Sparse { max_disk_size: None },
);
let args = builder.build_args().unwrap();
assert_eq!(vec!["mypath", "sparse", "--slice", "1"], args);
}
#[test]
fn sparse_args_max_size() {
let tools = FakeToolProvider::default();
let fvm_tool = tools.get_tool("fvm").unwrap();
let builder = FvmBuilder::new(
fvm_tool,
"mypath",
1,
false,
false,
FvmType::Sparse { max_disk_size: Some(500) },
);
let args = builder.build_args().unwrap();
assert_eq!(vec!["mypath", "sparse", "--max-disk-size", "500", "--slice", "1",], args);
}
#[test]
fn sparse_blob_args() {
let tools = FakeToolProvider::default();
let fvm_tool = tools.get_tool("fvm").unwrap();
let mut builder = FvmBuilder::new(
fvm_tool,
"mypath",
1,
false,
false,
FvmType::Sparse { max_disk_size: None },
);
builder.filesystem(Filesystem::EmptyData);
let args = builder.build_args().unwrap();
// TODO(https://fxbug.dev/42166034): Have assembly pass in an empty file and remove --with-empty-data.
assert_eq!(vec!["mypath", "sparse", "--slice", "1", "--with-empty-data",], args);
}
}