#![allow(missing_docs)]
use anyhow::{Context, Result};
use data_model::DataInit;
use std::{
fs::{File, OpenOptions},
io::Write,
mem::size_of,
os::unix::io::AsRawFd,
path::PathBuf,
};
pub mod util;
pub mod verity;
pub mod data_model;
mod sys;
use sys::*;
use util::*;
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);
fn dm_dev_create(dm: &DeviceMapper, ioctl: *mut DmIoctl) -> Result<i32> {
Ok(unsafe { _dm_dev_create(dm.0.as_raw_fd(), ioctl) }?)
}
fn dm_dev_suspend(dm: &DeviceMapper, ioctl: *mut DmIoctl) -> Result<i32> {
Ok(unsafe { _dm_dev_suspend(dm.0.as_raw_fd(), ioctl) }?)
}
fn dm_table_load(dm: &DeviceMapper, ioctl: *mut DmIoctl) -> Result<i32> {
Ok(unsafe { _dm_table_load(dm.0.as_raw_fd(), ioctl) }?)
}
fn dm_dev_remove(dm: &DeviceMapper, ioctl: *mut DmIoctl) -> Result<i32> {
Ok(unsafe { _dm_dev_remove(dm.0.as_raw_fd(), ioctl) }?)
}
#[repr(C)]
#[derive(Copy, Clone)]
struct DmTargetSpec {
sector_start: u64,
length: u64, status: i32,
next: u32,
target_type: [u8; DM_MAX_TYPE_NAME],
}
unsafe impl DataInit for DmTargetSpec {}
impl DmTargetSpec {
fn new(target_type: &str) -> Result<Self> {
let mut spec: Self = *DataInit::from_mut_slice(&mut [0; size_of::<Self>()])
.expect("failed to init DmTargetSpec");
spec.target_type
.as_mut()
.write_all(target_type.as_bytes())?;
Ok(spec)
}
}
impl DmIoctl {
fn new(name: &str) -> Result<DmIoctl> {
let mut data: Self =
*DataInit::from_mut_slice(&mut [0; size_of::<Self>()]).expect("failed to init DmIoctl");
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(())
}
}
pub struct DeviceMapper(File);
#[cfg(not(target_os = "android"))]
const MAPPER_CONTROL: &str = "/dev/mapper/control";
#[cfg(target_os = "android")]
const MAPPER_CONTROL: &str = "/dev/device-mapper";
#[cfg(not(target_os = "android"))]
const DEVICE_MAPPER_DEV: &str = "/dev/dm-";
#[cfg(target_os = "android")]
const DEVICE_MAPPER_DEV: &str = "/dev/block/dm-";
impl DeviceMapper {
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))
}
pub fn create_verity_device(&self, name: &str, target: &[u8]) -> Result<PathBuf> {
self.create_device(name, target, uuid(), false)
}
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(())
}
fn create_device(
&self,
name: &str,
target: &[u8],
uid: String,
writable: bool,
) -> Result<PathBuf> {
let mut data = DmIoctl::new(name)?;
data.set_uuid(&uid)?;
dm_dev_create(self, &mut data).context(format!(
"failed to create an empty device with name {}",
&name
))?;
let device_minor = unsafe { libc::minor(data.dev) };
#[cfg(target_os = "android")]
let device_minor = device_minor as u32;
let payload_size = size_of::<DmIoctl>() + target.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;
if !writable {
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);
dm_table_load(self, payload.as_mut_ptr() as *mut DmIoctl)
.context("failed to load table")?;
let mut data = DmIoctl::new(name)?;
dm_dev_suspend(self, &mut data).context("failed to activate")?;
let path = PathBuf::from(format!("{}{}", DEVICE_MAPPER_DEV, device_minor));
wait_for_path(&path)?;
Ok(path)
}
}
fn uuid() -> String {
uuid::Uuid::new_v4().to_string()
}