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()))
+    }
+}