Decouple apkdmverity from device mapper modules
We need to extend & reuse device mapper modules to construct other
targets such as crypt. This patch moves dm related modules to a separate
lib & change apkdmverity binary to use that library.
Other changes include making delete_device_deferred removing test
annotation from delete_device_deferred & changing visibility of
functions.
Test: atest apkdmverity.test
Bug: 250880499
Change-Id: I5894c362e2b85c6260931b4c9d90f7e3d602291c
diff --git a/libs/devicemapper/Android.bp b/libs/devicemapper/Android.bp
new file mode 100644
index 0000000..61ffa22
--- /dev/null
+++ b/libs/devicemapper/Android.bp
@@ -0,0 +1,29 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_defaults {
+ name: "libdm_rust.defaults",
+ crate_name: "dm",
+ srcs: ["src/lib.rs"],
+ edition: "2021",
+ prefer_rlib: true,
+ rustlibs: [
+ "libanyhow",
+ "libbitflags",
+ "liblibc",
+ "libdata_model",
+ "libnix",
+ "libuuid",
+ ],
+ multilib: {
+ lib32: {
+ enabled: false,
+ },
+ },
+}
+
+rust_library {
+ name: "libdm_rust",
+ defaults: ["libdm_rust.defaults"],
+}
diff --git a/libs/devicemapper/src/lib.rs b/libs/devicemapper/src/lib.rs
new file mode 100644
index 0000000..1bcaf1a
--- /dev/null
+++ b/libs/devicemapper/src/lib.rs
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// `dm` module implements part of the `device-mapper` ioctl interfaces. It currently supports
+// creation and deletion of the mapper device. It doesn't support other operations like querying
+// the status of the mapper device. And there's no plan to extend the support unless it is
+// required.
+//
+// Why in-house development? [`devicemapper`](https://crates.io/crates/devicemapper) is a public
+// Rust implementation of the device mapper APIs. However, it doesn't provide any abstraction for
+// the target-specific tables. User has to manually craft the table. Ironically, the library
+// provides a lot of APIs for the features that are not required for `apkdmverity` such as listing
+// the device mapper block devices that are currently listed in the kernel. Size is an important
+// criteria for Microdroid.
+
+//! A library to create device mapper spec & issue ioctls.
+
+#![allow(missing_docs)]
+
+use anyhow::{Context, Result};
+use data_model::DataInit;
+use std::fs::{File, OpenOptions};
+use std::io::Write;
+use std::mem::size_of;
+use std::os::unix::io::AsRawFd;
+use std::path::{Path, PathBuf};
+
+/// Expose util functions
+pub mod util;
+/// Exposes the DmVerityTarget & related builder
+pub mod verity;
+
+mod sys;
+use sys::*;
+use util::*;
+use verity::*;
+
+nix::ioctl_readwrite!(_dm_dev_create, DM_IOCTL, Cmd::DM_DEV_CREATE, DmIoctl);
+nix::ioctl_readwrite!(_dm_dev_suspend, DM_IOCTL, Cmd::DM_DEV_SUSPEND, DmIoctl);
+nix::ioctl_readwrite!(_dm_table_load, DM_IOCTL, Cmd::DM_TABLE_LOAD, DmIoctl);
+nix::ioctl_readwrite!(_dm_dev_remove, DM_IOCTL, Cmd::DM_DEV_REMOVE, DmIoctl);
+
+/// Create a new (mapper) device
+fn dm_dev_create(dm: &DeviceMapper, ioctl: *mut DmIoctl) -> Result<i32> {
+ // SAFETY: `ioctl` is copied into the kernel. It modifies the state in the kernel, not the
+ // state of this process in any way.
+ Ok(unsafe { _dm_dev_create(dm.0.as_raw_fd(), ioctl) }?)
+}
+
+fn dm_dev_suspend(dm: &DeviceMapper, ioctl: *mut DmIoctl) -> Result<i32> {
+ // SAFETY: `ioctl` is copied into the kernel. It modifies the state in the kernel, not the
+ // state of this process in any way.
+ Ok(unsafe { _dm_dev_suspend(dm.0.as_raw_fd(), ioctl) }?)
+}
+
+fn dm_table_load(dm: &DeviceMapper, ioctl: *mut DmIoctl) -> Result<i32> {
+ // SAFETY: `ioctl` is copied into the kernel. It modifies the state in the kernel, not the
+ // state of this process in any way.
+ Ok(unsafe { _dm_table_load(dm.0.as_raw_fd(), ioctl) }?)
+}
+
+fn dm_dev_remove(dm: &DeviceMapper, ioctl: *mut DmIoctl) -> Result<i32> {
+ // SAFETY: `ioctl` is copied into the kernel. It modifies the state in the kernel, not the
+ // state of this process in any way.
+ Ok(unsafe { _dm_dev_remove(dm.0.as_raw_fd(), ioctl) }?)
+}
+
+// `DmTargetSpec` is the header of the data structure for a device-mapper target. When doing the
+// ioctl, one of more `DmTargetSpec` (and its body) are appened to the `DmIoctl` struct.
+#[repr(C)]
+#[derive(Copy, Clone)]
+struct DmTargetSpec {
+ sector_start: u64,
+ length: u64, // number of 512 sectors
+ status: i32,
+ next: u32,
+ target_type: [u8; DM_MAX_TYPE_NAME],
+}
+
+// SAFETY: C struct is safe to be initialized from raw data
+unsafe impl DataInit for DmTargetSpec {}
+
+impl DmTargetSpec {
+ fn new(target_type: &str) -> Result<Self> {
+ // safe because the size of the array is the same as the size of the struct
+ let mut spec: Self = *DataInit::from_mut_slice(&mut [0; size_of::<Self>()]).unwrap();
+ spec.target_type.as_mut().write_all(target_type.as_bytes())?;
+ Ok(spec)
+ }
+}
+
+impl DmIoctl {
+ fn new(name: &str) -> Result<DmIoctl> {
+ // safe because the size of the array is the same as the size of the struct
+ let mut data: Self = *DataInit::from_mut_slice(&mut [0; size_of::<Self>()]).unwrap();
+ data.version[0] = DM_VERSION_MAJOR;
+ data.version[1] = DM_VERSION_MINOR;
+ data.version[2] = DM_VERSION_PATCHLEVEL;
+ data.data_size = size_of::<Self>() as u32;
+ data.data_start = 0;
+ data.name.as_mut().write_all(name.as_bytes())?;
+ Ok(data)
+ }
+
+ fn set_uuid(&mut self, uuid: &str) -> Result<()> {
+ let mut dst = self.uuid.as_mut();
+ dst.fill(0);
+ dst.write_all(uuid.as_bytes())?;
+ Ok(())
+ }
+}
+
+/// `DeviceMapper` is the entry point for the device mapper framework. It essentially is a file
+/// handle to "/dev/mapper/control".
+pub struct DeviceMapper(File);
+
+#[cfg(not(target_os = "android"))]
+const MAPPER_CONTROL: &str = "/dev/mapper/control";
+#[cfg(not(target_os = "android"))]
+const MAPPER_DEV_ROOT: &str = "/dev/mapper";
+
+#[cfg(target_os = "android")]
+const MAPPER_CONTROL: &str = "/dev/device-mapper";
+#[cfg(target_os = "android")]
+const MAPPER_DEV_ROOT: &str = "/dev/block/mapper";
+
+impl DeviceMapper {
+ /// Constructs a new `DeviceMapper` entrypoint. This is essentially the same as opening
+ /// "/dev/mapper/control".
+ pub fn new() -> Result<DeviceMapper> {
+ let f = OpenOptions::new()
+ .read(true)
+ .write(true)
+ .open(MAPPER_CONTROL)
+ .context(format!("failed to open {}", MAPPER_CONTROL))?;
+ Ok(DeviceMapper(f))
+ }
+
+ /// Creates a device mapper device and configure it according to the `target` specification.
+ /// The path to the generated device is "/dev/mapper/<name>".
+ pub fn create_verity_device(&self, name: &str, target: &DmVerityTarget) -> Result<PathBuf> {
+ // Step 1: create an empty device
+ let mut data = DmIoctl::new(name)?;
+ data.set_uuid(&uuid("apkver".as_bytes())?)?;
+ dm_dev_create(self, &mut data)
+ .context(format!("failed to create an empty device with name {}", &name))?;
+
+ // Step 2: load table onto the device
+ let payload_size = size_of::<DmIoctl>() + target.as_slice().len();
+
+ let mut data = DmIoctl::new(name)?;
+ data.data_size = payload_size as u32;
+ data.data_start = size_of::<DmIoctl>() as u32;
+ data.target_count = 1;
+ data.flags |= Flag::DM_READONLY_FLAG;
+
+ let mut payload = Vec::with_capacity(payload_size);
+ payload.extend_from_slice(data.as_slice());
+ payload.extend_from_slice(target.as_slice());
+ dm_table_load(self, payload.as_mut_ptr() as *mut DmIoctl)
+ .context("failed to load table")?;
+
+ // Step 3: activate the device (note: the term 'suspend' might be misleading, but it
+ // actually activates the table. See include/uapi/linux/dm-ioctl.h
+ let mut data = DmIoctl::new(name)?;
+ dm_dev_suspend(self, &mut data).context("failed to activate")?;
+
+ // Step 4: wait unti the device is created and return the device path
+ let path = Path::new(MAPPER_DEV_ROOT).join(&name);
+ wait_for_path(&path)?;
+ Ok(path)
+ }
+
+ /// Removes a mapper device
+ pub fn delete_device_deferred(&self, name: &str) -> Result<()> {
+ let mut data = DmIoctl::new(name)?;
+ data.flags |= Flag::DM_DEFERRED_REMOVE;
+ dm_dev_remove(self, &mut data)
+ .context(format!("failed to remove device with name {}", &name))?;
+ Ok(())
+ }
+}
+
+/// Used to derive a UUID that uniquely identifies a device mapper device when creating it.
+fn uuid(node_id: &[u8]) -> Result<String> {
+ use std::time::{SystemTime, UNIX_EPOCH};
+ use uuid::v1::{Context, Timestamp};
+ use uuid::Uuid;
+
+ let context = Context::new(0);
+ let now = SystemTime::now().duration_since(UNIX_EPOCH)?;
+ let ts = Timestamp::from_unix(&context, now.as_secs(), now.subsec_nanos());
+ let uuid = Uuid::new_v1(ts, node_id)?;
+ Ok(String::from(uuid.to_hyphenated().encode_lower(&mut Uuid::encode_buffer())))
+}
diff --git a/libs/devicemapper/src/sys.rs b/libs/devicemapper/src/sys.rs
new file mode 100644
index 0000000..e709bf0
--- /dev/null
+++ b/libs/devicemapper/src/sys.rs
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+use bitflags::bitflags;
+use data_model::DataInit;
+
+// UAPI for device mapper can be found at include/uapi/linux/dm-ioctl.h
+
+pub const DM_IOCTL: u8 = 0xfd;
+
+#[repr(u16)]
+#[allow(non_camel_case_types)]
+#[allow(dead_code)]
+pub enum Cmd {
+ DM_VERSION = 0,
+ DM_REMOVE_ALL,
+ DM_LIST_DEVICES,
+ DM_DEV_CREATE,
+ DM_DEV_REMOVE,
+ DM_DEV_RENAME,
+ DM_DEV_SUSPEND,
+ DM_DEV_STATUS,
+ DM_DEV_WAIT,
+ DM_TABLE_LOAD,
+ DM_TABLE_CLEAR,
+ DM_TABLE_DEPS,
+ DM_TABLE_STATUS,
+ DM_LIST_VERSIONS,
+ DM_TARGET_MSG,
+ DM_DEV_SET_GEOMETRY,
+}
+
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub struct DmIoctl {
+ pub version: [u32; 3],
+ pub data_size: u32,
+ pub data_start: u32,
+ pub target_count: u32,
+ pub open_count: i32,
+ pub flags: Flag,
+ pub event_nr: u32,
+ pub padding: u32,
+ pub dev: u64,
+ pub name: [u8; DM_NAME_LEN],
+ pub uuid: [u8; DM_UUID_LEN],
+ pub data: [u8; 7],
+}
+
+// SAFETY: C struct is safe to be initialized from raw data
+unsafe impl DataInit for DmIoctl {}
+
+pub const DM_VERSION_MAJOR: u32 = 4;
+pub const DM_VERSION_MINOR: u32 = 0;
+pub const DM_VERSION_PATCHLEVEL: u32 = 0;
+
+pub const DM_NAME_LEN: usize = 128;
+pub const DM_UUID_LEN: usize = 129;
+pub const DM_MAX_TYPE_NAME: usize = 16;
+
+bitflags! {
+ pub struct Flag: u32 {
+ const DM_READONLY_FLAG = 1 << 0;
+ const DM_SUSPEND_FLAG = 1 << 1;
+ const DM_PERSISTENT_DEV_FLAG = 1 << 3;
+ const DM_STATUS_TABLE_FLAG = 1 << 4;
+ const DM_ACTIVE_PRESENT_FLAG = 1 << 5;
+ const DM_INACTIVE_PRESENT_FLAG = 1 << 6;
+ const DM_BUFFER_FULL_FLAG = 1 << 8;
+ const DM_SKIP_BDGET_FLAG = 1 << 9;
+ const DM_SKIP_LOCKFS_FLAG = 1 << 10;
+ const DM_NOFLUSH_FLAG = 1 << 11;
+ const DM_QUERY_INACTIVE_TABLE_FLAG = 1 << 12;
+ const DM_UEVENT_GENERATED_FLAG = 1 << 13;
+ const DM_UUID_FLAG = 1 << 14;
+ const DM_SECURE_DATA_FLAG = 1 << 15;
+ const DM_DATA_OUT_FLAG = 1 << 16;
+ const DM_DEFERRED_REMOVE = 1 << 17;
+ const DM_INTERNAL_SUSPEND_FLAG = 1 << 18;
+ }
+}
diff --git a/libs/devicemapper/src/util.rs b/libs/devicemapper/src/util.rs
new file mode 100644
index 0000000..913f827
--- /dev/null
+++ b/libs/devicemapper/src/util.rs
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+use anyhow::{anyhow, bail, Result};
+use nix::sys::stat::FileStat;
+use std::fs::File;
+use std::os::unix::fs::FileTypeExt;
+use std::os::unix::io::AsRawFd;
+use std::path::Path;
+use std::thread;
+use std::time::{Duration, Instant};
+
+/// Returns when the file exists on the given `path` or timeout (1s) occurs.
+pub fn wait_for_path<P: AsRef<Path>>(path: P) -> Result<()> {
+ const TIMEOUT: Duration = Duration::from_secs(1);
+ const INTERVAL: Duration = Duration::from_millis(10);
+ let begin = Instant::now();
+ while !path.as_ref().exists() {
+ if begin.elapsed() > TIMEOUT {
+ bail!("{:?} not found. TIMEOUT.", path.as_ref());
+ }
+ thread::sleep(INTERVAL);
+ }
+ Ok(())
+}
+
+/// Returns hexadecimal reprentation of a given byte array.
+pub fn hexstring_from(s: &[u8]) -> String {
+ s.iter().map(|byte| format!("{:02x}", byte)).reduce(|i, j| i + &j).unwrap_or_default()
+}
+
+/// Parses a hexadecimal string into a byte array
+pub fn parse_hexstring(s: &str) -> Result<Vec<u8>> {
+ let len = s.len();
+ if len % 2 != 0 {
+ bail!("length {} is not even", len)
+ } else {
+ (0..len)
+ .step_by(2)
+ .map(|i| u8::from_str_radix(&s[i..i + 2], 16).map_err(|e| anyhow!(e)))
+ .collect()
+ }
+}
+
+/// fstat that accepts a path rather than FD
+pub fn fstat(p: &Path) -> Result<FileStat> {
+ let f = File::open(p)?;
+ Ok(nix::sys::stat::fstat(f.as_raw_fd())?)
+}
+
+// From include/uapi/linux/fs.h
+const BLK: u8 = 0x12;
+const BLKGETSIZE64: u8 = 114;
+nix::ioctl_read!(_blkgetsize64, BLK, BLKGETSIZE64, libc::size_t);
+
+/// Gets the size of a block device
+pub fn blkgetsize64(p: &Path) -> Result<u64> {
+ let f = File::open(p)?;
+ if !f.metadata()?.file_type().is_block_device() {
+ bail!("{:?} is not a block device", p);
+ }
+ let mut size: usize = 0;
+ // SAFETY: kernel copies the return value out to `size`. The file is kept open until the end of
+ // this function.
+ unsafe { _blkgetsize64(f.as_raw_fd(), &mut size) }?;
+ Ok(size as u64)
+}
diff --git a/libs/devicemapper/src/verity.rs b/libs/devicemapper/src/verity.rs
new file mode 100644
index 0000000..e0c5e52
--- /dev/null
+++ b/libs/devicemapper/src/verity.rs
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// `dm::verity` module implements the "verity" target in the device mapper framework. Specifically,
+// it provides `DmVerityTargetBuilder` struct which is used to construct a `DmVerityTarget` struct
+// which is then given to `DeviceMapper` to create a mapper device.
+
+use anyhow::{bail, Context, Result};
+use data_model::DataInit;
+use std::io::Write;
+use std::mem::size_of;
+use std::path::Path;
+
+use crate::util::*;
+use crate::DmTargetSpec;
+
+// The UAPI for the verity target is here.
+// https://www.kernel.org/doc/Documentation/device-mapper/verity.txt
+
+/// Device-Mapper’s “verity” target provides transparent integrity checking of block devices using
+/// a cryptographic digest provided by the kernel crypto API
+pub struct DmVerityTarget(Box<[u8]>);
+
+/// Version of the verity target spec.
+pub enum DmVerityVersion {
+ /// Only `1` is supported.
+ V1,
+}
+
+/// The hash algorithm to use. SHA256 and SHA512 are supported.
+#[allow(dead_code)]
+pub enum DmVerityHashAlgorithm {
+ /// sha with 256 bit hash
+ SHA256,
+ /// sha with 512 bit hash
+ SHA512,
+}
+
+/// A builder that constructs `DmVerityTarget` struct.
+pub struct DmVerityTargetBuilder<'a> {
+ version: DmVerityVersion,
+ data_device: Option<&'a Path>,
+ data_size: u64,
+ hash_device: Option<&'a Path>,
+ hash_algorithm: DmVerityHashAlgorithm,
+ root_digest: Option<&'a [u8]>,
+ salt: Option<&'a [u8]>,
+}
+
+impl DmVerityTarget {
+ /// flatten into slice
+ pub fn as_slice(&self) -> &[u8] {
+ self.0.as_ref()
+ }
+}
+
+impl<'a> Default for DmVerityTargetBuilder<'a> {
+ fn default() -> Self {
+ DmVerityTargetBuilder {
+ version: DmVerityVersion::V1,
+ data_device: None,
+ data_size: 0,
+ hash_device: None,
+ hash_algorithm: DmVerityHashAlgorithm::SHA256,
+ root_digest: None,
+ salt: None,
+ }
+ }
+}
+
+impl<'a> DmVerityTargetBuilder<'a> {
+ /// Sets the device that will be used as the data device (i.e. providing actual data).
+ pub fn data_device(&mut self, p: &'a Path, size: u64) -> &mut Self {
+ self.data_device = Some(p);
+ self.data_size = size;
+ self
+ }
+
+ /// Sets the device that provides the merkle tree.
+ pub fn hash_device(&mut self, p: &'a Path) -> &mut Self {
+ self.hash_device = Some(p);
+ self
+ }
+
+ /// Sets the hash algorithm that the merkle tree is using.
+ pub fn hash_algorithm(&mut self, algo: DmVerityHashAlgorithm) -> &mut Self {
+ self.hash_algorithm = algo;
+ self
+ }
+
+ /// Sets the root digest of the merkle tree. The format is hexadecimal string.
+ pub fn root_digest(&mut self, digest: &'a [u8]) -> &mut Self {
+ self.root_digest = Some(digest);
+ self
+ }
+
+ /// Sets the salt used when creating the merkle tree. Note that this is empty for merkle trees
+ /// created following the APK signature scheme V4.
+ pub fn salt(&mut self, salt: &'a [u8]) -> &mut Self {
+ self.salt = Some(salt);
+ self
+ }
+
+ /// Constructs a `DmVerityTarget`.
+ pub fn build(&self) -> Result<DmVerityTarget> {
+ // The `DmVerityTarget` struct actually is a flattened data consisting of a header and
+ // body. The format of the header is `dm_target_spec` as defined in
+ // include/uapi/linux/dm-ioctl.h. The format of the body, in case of `verity` target is
+ // https://www.kernel.org/doc/Documentation/device-mapper/verity.txt
+ //
+ // Step 1: check the validity of the inputs and extra additional data (e.g. block size)
+ // from them.
+ let version = match self.version {
+ DmVerityVersion::V1 => 1,
+ };
+
+ let data_device_path = self
+ .data_device
+ .context("data device is not set")?
+ .to_str()
+ .context("data device path is not encoded in utf8")?;
+ let stat = fstat(self.data_device.unwrap())?; // safe; checked just above
+ let data_block_size = stat.st_blksize as u64;
+ let data_size = self.data_size;
+ let num_data_blocks = data_size / data_block_size;
+
+ let hash_device_path = self
+ .hash_device
+ .context("hash device is not set")?
+ .to_str()
+ .context("hash device path is not encoded in utf8")?;
+ let stat = fstat(self.data_device.unwrap())?; // safe; checked just above
+ let hash_block_size = stat.st_blksize;
+
+ let hash_algorithm = match self.hash_algorithm {
+ DmVerityHashAlgorithm::SHA256 => "sha256",
+ DmVerityHashAlgorithm::SHA512 => "sha512",
+ };
+
+ let root_digest = if let Some(root_digest) = self.root_digest {
+ hexstring_from(root_digest)
+ } else {
+ bail!("root digest is not set")
+ };
+
+ let salt = if self.salt.is_none() || self.salt.unwrap().is_empty() {
+ "-".to_string() // Note. It's not an empty string!
+ } else {
+ hexstring_from(self.salt.unwrap())
+ };
+
+ // Step2: serialize the information according to the spec, which is ...
+ // DmTargetSpec{...}
+ // <version> <dev> <hash_dev>
+ // <data_block_size> <hash_block_size>
+ // <num_data_blocks> <hash_start_block>
+ // <algorithm> <digest> <salt>
+ // [<#opt_params> <opt_params>]
+ // null terminator
+
+ // TODO(jiyong): support the optional parameters... if needed.
+ let mut body = String::new();
+ use std::fmt::Write;
+ write!(&mut body, "{} ", version)?;
+ write!(&mut body, "{} ", data_device_path)?;
+ write!(&mut body, "{} ", hash_device_path)?;
+ write!(&mut body, "{} ", data_block_size)?;
+ write!(&mut body, "{} ", hash_block_size)?;
+ write!(&mut body, "{} ", num_data_blocks)?;
+ write!(&mut body, "{} ", 0)?; // hash_start_block
+ write!(&mut body, "{} ", hash_algorithm)?;
+ write!(&mut body, "{} ", root_digest)?;
+ write!(&mut body, "{}", salt)?;
+ write!(&mut body, "\0")?; // null terminator
+
+ let size = size_of::<DmTargetSpec>() + body.len();
+ let aligned_size = (size + 7) & !7; // align to 8 byte boundaries
+ let padding = aligned_size - size;
+ let mut header = DmTargetSpec::new("verity")?;
+ header.sector_start = 0;
+ header.length = data_size / 512; // number of 512-byte sectors
+ header.next = aligned_size as u32;
+
+ let mut buf = Vec::with_capacity(aligned_size);
+ buf.write_all(header.as_slice())?;
+ buf.write_all(body.as_bytes())?;
+ buf.write_all(vec![0; padding].as_slice())?;
+ Ok(DmVerityTarget(buf.into_boxed_slice()))
+ }
+}