blob: ec4248c439dad678a6e900a86012e08b5f72d7cd [file] [log] [blame]
Jiyong Park86c9b082021-06-04 19:03:48 +09001/*
2 * Copyright (C) 2021 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17// `dm` module implements part of the `device-mapper` ioctl interfaces. It currently supports
18// creation and deletion of the mapper device. It doesn't support other operations like querying
19// the status of the mapper device. And there's no plan to extend the support unless it is
20// required.
21//
22// Why in-house development? [`devicemapper`](https://crates.io/crates/devicemapper) is a public
23// Rust implementation of the device mapper APIs. However, it doesn't provide any abstraction for
24// the target-specific tables. User has to manually craft the table. Ironically, the library
25// provides a lot of APIs for the features that are not required for `apkdmverity` such as listing
26// the device mapper block devices that are currently listed in the kernel. Size is an important
27// criteria for Microdroid.
28
29use crate::util::*;
30
31use anyhow::Result;
32use std::fs::{File, OpenOptions};
33use std::io::Write;
34use std::mem::size_of;
35use std::os::unix::io::AsRawFd;
36use std::path::{Path, PathBuf};
Jiyong Park86c9b082021-06-04 19:03:48 +090037
38mod sys;
39mod verity;
40use sys::*;
41pub use verity::*;
42
43nix::ioctl_readwrite!(_dm_dev_create, DM_IOCTL, Cmd::DM_DEV_CREATE, DmIoctl);
44nix::ioctl_readwrite!(_dm_dev_remove, DM_IOCTL, Cmd::DM_DEV_REMOVE, DmIoctl);
45nix::ioctl_readwrite!(_dm_dev_suspend, DM_IOCTL, Cmd::DM_DEV_SUSPEND, DmIoctl);
46nix::ioctl_readwrite!(_dm_table_load, DM_IOCTL, Cmd::DM_TABLE_LOAD, DmIoctl);
47
48fn dm_dev_create(dm: &DeviceMapper, ioctl: *mut DmIoctl) -> Result<i32> {
49 // SAFETY: `ioctl` is copied into the kernel. It modifies the state in the kernel, not the
50 // state of this process in any way.
51 Ok(unsafe { _dm_dev_create(dm.0.as_raw_fd(), ioctl) }?)
52}
53
54fn dm_dev_remove(dm: &DeviceMapper, ioctl: *mut DmIoctl) -> Result<i32> {
55 // SAFETY: `ioctl` is copied into the kernel. It modifies the state in the kernel, not the
56 // state of this process in any way.
57 Ok(unsafe { _dm_dev_remove(dm.0.as_raw_fd(), ioctl) }?)
58}
59
60fn dm_dev_suspend(dm: &DeviceMapper, ioctl: *mut DmIoctl) -> Result<i32> {
61 // SAFETY: `ioctl` is copied into the kernel. It modifies the state in the kernel, not the
62 // state of this process in any way.
63 Ok(unsafe { _dm_dev_suspend(dm.0.as_raw_fd(), ioctl) }?)
64}
65
66fn dm_table_load(dm: &DeviceMapper, ioctl: *mut DmIoctl) -> Result<i32> {
67 // SAFETY: `ioctl` is copied into the kernel. It modifies the state in the kernel, not the
68 // state of this process in any way.
69 Ok(unsafe { _dm_table_load(dm.0.as_raw_fd(), ioctl) }?)
70}
71
72// `DmTargetSpec` is the header of the data structure for a device-mapper target. When doing the
73// ioctl, one of more `DmTargetSpec` (and its body) are appened to the `DmIoctl` struct.
74#[repr(C)]
75struct DmTargetSpec {
76 sector_start: u64,
77 length: u64, // number of 512 sectors
78 status: i32,
79 next: u32,
80 target_type: [u8; DM_MAX_TYPE_NAME],
81}
82
83impl DmTargetSpec {
84 fn new(target_type: &str) -> Result<Self> {
85 // SAFETY: zero initialized C struct is safe
86 let mut spec = unsafe { std::mem::MaybeUninit::<Self>::zeroed().assume_init() };
87 spec.target_type.as_mut().write_all(target_type.as_bytes())?;
88 Ok(spec)
89 }
90
91 fn as_u8_slice(&self) -> &[u8; size_of::<Self>()] {
92 // SAFETY: lifetime of the output reference isn't changed.
93 unsafe { std::mem::transmute::<&Self, &[u8; size_of::<Self>()]>(&self) }
94 }
95}
96
97impl DmIoctl {
98 fn new(name: &str) -> Result<DmIoctl> {
99 // SAFETY: zero initialized C struct is safe
100 let mut data = unsafe { std::mem::MaybeUninit::<Self>::zeroed().assume_init() };
101 data.version[0] = DM_VERSION_MAJOR;
102 data.version[1] = DM_VERSION_MINOR;
103 data.version[2] = DM_VERSION_PATCHLEVEL;
104 data.data_size = size_of::<Self>() as u32;
105 data.data_start = 0;
106 data.name.as_mut().write_all(name.as_bytes())?;
107 Ok(data)
108 }
109
110 fn set_uuid(&mut self, uuid: &str) -> Result<()> {
111 let mut dst = self.uuid.as_mut();
112 dst.fill(0);
113 dst.write_all(uuid.as_bytes())?;
114 Ok(())
115 }
116
117 fn as_u8_slice(&self) -> &[u8; size_of::<Self>()] {
118 // SAFETY: lifetime of the output reference isn't changed.
119 unsafe { std::mem::transmute::<&Self, &[u8; size_of::<Self>()]>(&self) }
120 }
121}
122
123/// `DeviceMapper` is the entry point for the device mapper framework. It essentially is a file
124/// handle to "/dev/mapper/control".
125pub struct DeviceMapper(File);
126
127impl DeviceMapper {
128 /// Constructs a new `DeviceMapper` entrypoint. This is essentially the same as opening
129 /// "/dev/mapper/control".
130 pub fn new() -> Result<DeviceMapper> {
131 let f = OpenOptions::new().read(true).write(true).open("/dev/mapper/control")?;
132 Ok(DeviceMapper(f))
133 }
134
135 /// Creates a device mapper device and configure it according to the `target` specification.
136 /// The path to the generated device is "/dev/mapper/<name>".
137 pub fn create_device(&self, name: &str, target: &DmVerityTarget) -> Result<PathBuf> {
138 // Step 1: create an empty device
139 let mut data = DmIoctl::new(&name)?;
Jiyong Parkf02061f2021-06-07 09:44:44 +0900140 data.set_uuid(&uuid()?)?;
Jiyong Park86c9b082021-06-04 19:03:48 +0900141 dm_dev_create(&self, &mut data)?;
142
143 // Step 2: load table onto the device
144 let payload_size = size_of::<DmIoctl>() + target.as_u8_slice().len();
145
146 let mut data = DmIoctl::new(&name)?;
147 data.data_size = payload_size as u32;
148 data.data_start = size_of::<DmIoctl>() as u32;
149 data.target_count = 1;
150 data.flags |= Flag::DM_READONLY_FLAG;
151
152 let mut payload = Vec::with_capacity(payload_size);
153 payload.extend_from_slice(&data.as_u8_slice()[..]);
154 payload.extend_from_slice(&target.as_u8_slice()[..]);
155 dm_table_load(&self, payload.as_mut_ptr() as *mut DmIoctl)?;
156
157 // Step 3: activate the device (note: the term 'suspend' might be misleading, but it
158 // actually activates the table. See include/uapi/linux/dm-ioctl.h
159 let mut data = DmIoctl::new(&name)?;
160 dm_dev_suspend(&self, &mut data)?;
161
162 // Step 4: wait unti the device is created and return the device path
163 let path = Path::new("/dev/mapper").join(&name);
164 wait_for_path(&path)?;
165 Ok(path)
166 }
167
168 /// Removes a mapper device
169 pub fn delete_device_deferred(&self, name: &str) -> Result<()> {
170 let mut data = DmIoctl::new(&name)?;
171 data.flags |= Flag::DM_DEFERRED_REMOVE;
172 dm_dev_remove(&self, &mut data)?;
173 Ok(())
174 }
175}
176
177/// Used to derive a UUID that uniquely identifies a device mapper device when creating it.
Jiyong Parkf02061f2021-06-07 09:44:44 +0900178fn uuid() -> Result<String> {
179 use std::time::{SystemTime, UNIX_EPOCH};
180 use uuid::v1::{Context, Timestamp};
181 use uuid::Uuid;
182
183 let context = Context::new(0);
184 let now = SystemTime::now().duration_since(UNIX_EPOCH)?;
185 let ts = Timestamp::from_unix(&context, now.as_secs(), now.subsec_nanos());
186 let uuid = Uuid::new_v1(ts, "apkver".as_bytes())?;
187 Ok(String::from(uuid.to_hyphenated().encode_lower(&mut Uuid::encode_buffer())))
Jiyong Park86c9b082021-06-04 19:03:48 +0900188}