blob: d1cd2eb78bdff99719c567b5f3a5479333f52752 [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};
37use uuid::Uuid;
38
39mod sys;
40mod verity;
41use sys::*;
42pub use verity::*;
43
44nix::ioctl_readwrite!(_dm_dev_create, DM_IOCTL, Cmd::DM_DEV_CREATE, DmIoctl);
45nix::ioctl_readwrite!(_dm_dev_remove, DM_IOCTL, Cmd::DM_DEV_REMOVE, DmIoctl);
46nix::ioctl_readwrite!(_dm_dev_suspend, DM_IOCTL, Cmd::DM_DEV_SUSPEND, DmIoctl);
47nix::ioctl_readwrite!(_dm_table_load, DM_IOCTL, Cmd::DM_TABLE_LOAD, DmIoctl);
48
49fn dm_dev_create(dm: &DeviceMapper, ioctl: *mut DmIoctl) -> Result<i32> {
50 // SAFETY: `ioctl` is copied into the kernel. It modifies the state in the kernel, not the
51 // state of this process in any way.
52 Ok(unsafe { _dm_dev_create(dm.0.as_raw_fd(), ioctl) }?)
53}
54
55fn dm_dev_remove(dm: &DeviceMapper, ioctl: *mut DmIoctl) -> Result<i32> {
56 // SAFETY: `ioctl` is copied into the kernel. It modifies the state in the kernel, not the
57 // state of this process in any way.
58 Ok(unsafe { _dm_dev_remove(dm.0.as_raw_fd(), ioctl) }?)
59}
60
61fn dm_dev_suspend(dm: &DeviceMapper, ioctl: *mut DmIoctl) -> Result<i32> {
62 // SAFETY: `ioctl` is copied into the kernel. It modifies the state in the kernel, not the
63 // state of this process in any way.
64 Ok(unsafe { _dm_dev_suspend(dm.0.as_raw_fd(), ioctl) }?)
65}
66
67fn dm_table_load(dm: &DeviceMapper, ioctl: *mut DmIoctl) -> Result<i32> {
68 // SAFETY: `ioctl` is copied into the kernel. It modifies the state in the kernel, not the
69 // state of this process in any way.
70 Ok(unsafe { _dm_table_load(dm.0.as_raw_fd(), ioctl) }?)
71}
72
73// `DmTargetSpec` is the header of the data structure for a device-mapper target. When doing the
74// ioctl, one of more `DmTargetSpec` (and its body) are appened to the `DmIoctl` struct.
75#[repr(C)]
76struct DmTargetSpec {
77 sector_start: u64,
78 length: u64, // number of 512 sectors
79 status: i32,
80 next: u32,
81 target_type: [u8; DM_MAX_TYPE_NAME],
82}
83
84impl DmTargetSpec {
85 fn new(target_type: &str) -> Result<Self> {
86 // SAFETY: zero initialized C struct is safe
87 let mut spec = unsafe { std::mem::MaybeUninit::<Self>::zeroed().assume_init() };
88 spec.target_type.as_mut().write_all(target_type.as_bytes())?;
89 Ok(spec)
90 }
91
92 fn as_u8_slice(&self) -> &[u8; size_of::<Self>()] {
93 // SAFETY: lifetime of the output reference isn't changed.
94 unsafe { std::mem::transmute::<&Self, &[u8; size_of::<Self>()]>(&self) }
95 }
96}
97
98impl DmIoctl {
99 fn new(name: &str) -> Result<DmIoctl> {
100 // SAFETY: zero initialized C struct is safe
101 let mut data = unsafe { std::mem::MaybeUninit::<Self>::zeroed().assume_init() };
102 data.version[0] = DM_VERSION_MAJOR;
103 data.version[1] = DM_VERSION_MINOR;
104 data.version[2] = DM_VERSION_PATCHLEVEL;
105 data.data_size = size_of::<Self>() as u32;
106 data.data_start = 0;
107 data.name.as_mut().write_all(name.as_bytes())?;
108 Ok(data)
109 }
110
111 fn set_uuid(&mut self, uuid: &str) -> Result<()> {
112 let mut dst = self.uuid.as_mut();
113 dst.fill(0);
114 dst.write_all(uuid.as_bytes())?;
115 Ok(())
116 }
117
118 fn as_u8_slice(&self) -> &[u8; size_of::<Self>()] {
119 // SAFETY: lifetime of the output reference isn't changed.
120 unsafe { std::mem::transmute::<&Self, &[u8; size_of::<Self>()]>(&self) }
121 }
122}
123
124/// `DeviceMapper` is the entry point for the device mapper framework. It essentially is a file
125/// handle to "/dev/mapper/control".
126pub struct DeviceMapper(File);
127
128impl DeviceMapper {
129 /// Constructs a new `DeviceMapper` entrypoint. This is essentially the same as opening
130 /// "/dev/mapper/control".
131 pub fn new() -> Result<DeviceMapper> {
132 let f = OpenOptions::new().read(true).write(true).open("/dev/mapper/control")?;
133 Ok(DeviceMapper(f))
134 }
135
136 /// Creates a device mapper device and configure it according to the `target` specification.
137 /// The path to the generated device is "/dev/mapper/<name>".
138 pub fn create_device(&self, name: &str, target: &DmVerityTarget) -> Result<PathBuf> {
139 // Step 1: create an empty device
140 let mut data = DmIoctl::new(&name)?;
141 data.set_uuid(&uuid())?;
142 dm_dev_create(&self, &mut data)?;
143
144 // Step 2: load table onto the device
145 let payload_size = size_of::<DmIoctl>() + target.as_u8_slice().len();
146
147 let mut data = DmIoctl::new(&name)?;
148 data.data_size = payload_size as u32;
149 data.data_start = size_of::<DmIoctl>() as u32;
150 data.target_count = 1;
151 data.flags |= Flag::DM_READONLY_FLAG;
152
153 let mut payload = Vec::with_capacity(payload_size);
154 payload.extend_from_slice(&data.as_u8_slice()[..]);
155 payload.extend_from_slice(&target.as_u8_slice()[..]);
156 dm_table_load(&self, payload.as_mut_ptr() as *mut DmIoctl)?;
157
158 // Step 3: activate the device (note: the term 'suspend' might be misleading, but it
159 // actually activates the table. See include/uapi/linux/dm-ioctl.h
160 let mut data = DmIoctl::new(&name)?;
161 dm_dev_suspend(&self, &mut data)?;
162
163 // Step 4: wait unti the device is created and return the device path
164 let path = Path::new("/dev/mapper").join(&name);
165 wait_for_path(&path)?;
166 Ok(path)
167 }
168
169 /// Removes a mapper device
170 pub fn delete_device_deferred(&self, name: &str) -> Result<()> {
171 let mut data = DmIoctl::new(&name)?;
172 data.flags |= Flag::DM_DEFERRED_REMOVE;
173 dm_dev_remove(&self, &mut data)?;
174 Ok(())
175 }
176}
177
178/// Used to derive a UUID that uniquely identifies a device mapper device when creating it.
179// TODO(jiyong): the v4 is a randomly generated UUID. We might want another version of UUID (e.g.
180// v3) where we can specify the namespace so that we can easily identify UUID's created for this
181// purpose. For now, this random UUID is fine because we are expected to have only "one" instance
182// of dm-verity device in Microdroid.
183fn uuid() -> String {
184 String::from(Uuid::new_v4().to_hyphenated().encode_lower(&mut Uuid::encode_buffer()))
185}