1 // Copyright 2025, The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 //! This module defines block device utilities used for mmd.
16
17 use std::fs;
18 use std::os::unix::fs::MetadataExt;
19 use std::path::Path;
20 use std::path::PathBuf;
21
22 /// Error from block device operations.
23 #[derive(Debug, thiserror::Error)]
24 pub enum BlockDeviceError {
25 /// Failed to perform an IO operation on some block device file
26 #[error("failed to perform IOs on a block device file: {0}")]
27 DeviceFileIo(#[from] std::io::Error),
28 /// Failed to get input file metadata
29 #[error("failed to get input file metadata: {0}")]
30 InputFileMetadata(std::io::Error),
31 /// Failed to parse device queue depth
32 #[error("failed to parse device queue depth: {0}")]
33 ParseDeviceQueueDepth(#[from] std::num::ParseIntError),
34 /// Block dev path is invalid
35 #[error("block device path {0} is invalid: {1}")]
36 InvalidBlockDevicePath(PathBuf, String),
37 }
38
39 type Result<T> = std::result::Result<T, BlockDeviceError>;
40
41 /// Clear device IO scheduler by setting the scheduler to none.
42 ///
43 /// Only works for Kernels version v4.1 and after. Kernels before v4.1 only support 'noop'.
44 /// However, Android does not need to support kernels lower than v4.1 because v6.1 is the minimum
45 /// version on Android 15.
clear_block_device_scheduler(device_name: &str) -> std::io::Result<()>46 pub fn clear_block_device_scheduler(device_name: &str) -> std::io::Result<()> {
47 fs::write(format!("/sys/block/{device_name}/queue/scheduler"), "none")
48 }
49
50 /// Configure block device `nr_requests` to be the same as the queue depth of the block device backing `file_path`.
configure_block_device_queue_depth<P: AsRef<Path>>( device_name: &str, file_path: P, ) -> Result<()>51 pub fn configure_block_device_queue_depth<P: AsRef<Path>>(
52 device_name: &str,
53 file_path: P,
54 ) -> Result<()> {
55 configure_block_device_queue_depth_with_sysfs(device_name, file_path.as_ref(), "/sys")
56 }
57
58 // Using `&str` type instead of `&Path` for `sysfs_path` to make it easier for formatting block
59 // device paths. It should be fince since this is a private method introduced for testability only.
configure_block_device_queue_depth_with_sysfs( device_name: &str, file_path: &Path, sysfs_path: &str, ) -> Result<()>60 fn configure_block_device_queue_depth_with_sysfs(
61 device_name: &str,
62 file_path: &Path,
63 sysfs_path: &str,
64 ) -> Result<()> {
65 let file_backing_device = find_backing_block_device(file_path, sysfs_path)?;
66
67 let backing_device_queue_depth =
68 fs::read_to_string(format!("{sysfs_path}/class/block/{file_backing_device}/mq/0/nr_tags"))?;
69 let backing_device_queue_depth = backing_device_queue_depth.trim().parse::<u32>()?;
70
71 fs::write(
72 format!("{sysfs_path}/class/block/{device_name}/queue/nr_requests"),
73 backing_device_queue_depth.to_string(),
74 )?;
75 Ok(())
76 }
77
78 /// For file `file_path`, retrieve the block device backing the filesystem on
79 /// which the file exists.
find_backing_block_device(file_path: &Path, sysfs_path: &str) -> Result<String>80 fn find_backing_block_device(file_path: &Path, sysfs_path: &str) -> Result<String> {
81 let mut device_name = get_block_device_name(file_path, sysfs_path)?;
82
83 while let Some(parent_device) = get_parent_block_device(&device_name, sysfs_path)? {
84 device_name = parent_device;
85 }
86
87 device_name = partition_parent(&device_name, sysfs_path)?;
88
89 Ok(device_name)
90 }
91
92 /// Get immediate block device name backing `file_path`.
93 ///
94 /// By following the symlink `/sys/dev/block/{major}:{minor}` to the actual device path.
get_block_device_name(file_path: &Path, sysfs_path: &str) -> Result<String>95 fn get_block_device_name(file_path: &Path, sysfs_path: &str) -> Result<String> {
96 let devnum =
97 fs::metadata(file_path).map_err(BlockDeviceError::InputFileMetadata)?.dev() as libc::dev_t;
98 // TODO: b/388993276 - Use nix::sys::stat::major|minor once they are configured to be built for Android.
99 // SAFETY: devnum should be valid because it's from file metadata.
100 let (major, minor) = unsafe { (libc::major(devnum), libc::minor(devnum)) };
101 let device_path = std::fs::canonicalize(format!("{sysfs_path}/dev/block/{major}:{minor}"))?;
102 Ok(device_path
103 .file_name()
104 .ok_or_else(|| {
105 BlockDeviceError::InvalidBlockDevicePath(
106 device_path.clone(),
107 "block device real path doesn't have a file name".to_string(),
108 )
109 })?
110 .to_str()
111 .ok_or_else(|| {
112 BlockDeviceError::InvalidBlockDevicePath(
113 device_path.clone(),
114 "block device name is not valid Unicode".to_string(),
115 )
116 })?
117 .to_string())
118 }
119
120 /// Returns a parent block device of a dm device with the given name.
121 ///
122 /// None will be returned if:
123 /// * Given path doesn't correspond to a dm device.
124 /// * A dm device is based on top of more than one block devices.
get_parent_block_device(device_name: &str, sysfs_path: &str) -> std::io::Result<Option<String>>125 fn get_parent_block_device(device_name: &str, sysfs_path: &str) -> std::io::Result<Option<String>> {
126 if !device_name.starts_with("dm-") {
127 // Reached bottom of the device mapper stack.
128 return Ok(None);
129 }
130 let mut sub_device_name = None;
131 for entry in fs::read_dir(format!("{sysfs_path}/block/{device_name}/slaves"))? {
132 let entry = entry?;
133 if entry.file_type()?.is_symlink() {
134 if sub_device_name.is_some() {
135 // Too many slaves. Returning None to be consistent with fs_mgr's libdm implementation:
136 // https://cs.android.com/android/platform/superproject/main/+/main:system/core/fs_mgr/libdm/dm.cpp;l=677-678;drc=2bd1c1b20871bcf4ef4660beaa218f2c2bce4630
137 return Ok(None);
138 }
139 sub_device_name = Some(entry.file_name().to_string_lossy().to_string());
140 }
141 }
142 Ok(sub_device_name)
143 }
144
145 /// Returns the parent device of a partition.
146 ///
147 /// Converts e.g. "sda26" into "sda".
partition_parent(device_name: &str, sysfs_path: &str) -> std::io::Result<String>148 fn partition_parent(device_name: &str, sysfs_path: &str) -> std::io::Result<String> {
149 for entry in fs::read_dir(format!("{sysfs_path}/class/block"))? {
150 let name = entry?.file_name();
151 let name = name.to_string_lossy();
152
153 if name.starts_with('.') {
154 continue;
155 }
156
157 if fs::exists(format!("{sysfs_path}/class/block/{name}/{device_name}"))? {
158 return Ok(name.to_string());
159 }
160 }
161 Ok(device_name.to_string())
162 }
163
164 #[cfg(test)]
165 mod tests {
166 use std::os::unix::fs::symlink;
167
168 use tempfile::tempdir;
169 use tempfile::TempDir;
170
171 use super::*;
172
173 enum FakeFs<'a> {
174 Symlink(&'a str, &'a str),
175 File(&'a str, &'a str),
176 Dir(&'a str),
177 BackingDevice(&'a Path, &'a str),
178 }
179
180 impl FakeFs<'_> {
build(entries: &[Self]) -> TempDir181 fn build(entries: &[Self]) -> TempDir {
182 let tempdir = tempdir().unwrap();
183 let root = tempdir.path();
184 for entry in entries {
185 match entry {
186 Self::Symlink(link, original) => {
187 let link = root.join(link.trim_start_matches("/"));
188 let original = root.join(original.trim_start_matches("/"));
189 fs::create_dir_all(link.parent().unwrap()).unwrap();
190 symlink(original, link).unwrap();
191 }
192 Self::File(path, content) => {
193 let path = root.join(path.trim_start_matches("/"));
194 fs::create_dir_all(path.parent().unwrap()).unwrap();
195 fs::write(path, content).unwrap();
196 }
197 Self::Dir(path) => {
198 fs::create_dir_all(root.join(path.trim_start_matches("/"))).unwrap();
199 }
200 Self::BackingDevice(file_path, device_path) => {
201 let device_path = root.join(device_path.trim_start_matches("/"));
202 let devnum = fs::metadata(file_path).unwrap().dev() as libc::dev_t;
203 // TODO: b/388993276 - Use nix::sys::stat::major|minor once they are configured to be built for Android.
204 // SAFETY: devnum should be valid because it's from file metadata.
205 let (major, minor) = unsafe { (libc::major(devnum), libc::minor(devnum)) };
206 let link = root.join(format!("sys/dev/block/{major}:{minor}"));
207 fs::create_dir_all(link.parent().unwrap()).unwrap();
208 symlink(device_path, link).unwrap();
209 }
210 }
211 }
212 tempdir
213 }
214 }
215
216 #[test]
find_backing_block_device_simple()217 fn find_backing_block_device_simple() {
218 let file = tempfile::NamedTempFile::new().unwrap();
219 let fake_fs = FakeFs::build(&[
220 FakeFs::Dir("/sys/devices/platform/block/vda/"),
221 FakeFs::Symlink("/sys/class/block/vda", "/sys/devices/platform/block/vda/"),
222 FakeFs::BackingDevice(file.path(), "/sys/devices/platform/block/vda"),
223 ]);
224
225 assert_eq!(
226 find_backing_block_device(file.path(), fake_fs.path().join("sys").to_str().unwrap())
227 .unwrap(),
228 "vda"
229 );
230 }
231
232 #[test]
find_backing_block_device_device_mapper()233 fn find_backing_block_device_device_mapper() {
234 let file = tempfile::NamedTempFile::new().unwrap();
235 let fake_fs = FakeFs::build(&[
236 FakeFs::Dir("/sys/devices/platform/block/vda/"),
237 FakeFs::Dir("/sys/devices/virtual/block/dm-0/"),
238 FakeFs::Dir("/sys/devices/virtual/block/dm-7/"),
239 FakeFs::Symlink("/sys/block/dm-0/slaves/vda", "/sys/devices/platform/block/vda"),
240 FakeFs::Symlink("/sys/block/dm-7/slaves/dm-0", "/sys/devices/virtual/block/dm-0"),
241 FakeFs::Symlink("/sys/class/block/vda", "/sys/devices/platform/block/vda"),
242 FakeFs::BackingDevice(file.path(), "/sys/devices/virtual/block/dm-7"),
243 ]);
244
245 assert_eq!(
246 find_backing_block_device(file.path(), fake_fs.path().join("sys").to_str().unwrap())
247 .unwrap(),
248 "vda"
249 );
250 }
251
252 #[test]
find_backing_block_device_parent_partition()253 fn find_backing_block_device_parent_partition() {
254 let file = tempfile::NamedTempFile::new().unwrap();
255 let fake_fs = FakeFs::build(&[
256 FakeFs::Dir("/sys/devices/platform/block/vda/vda2"),
257 FakeFs::Symlink("/sys/class/block/vda", "/sys/devices/platform/block/vda"),
258 FakeFs::Symlink("/sys/class/block/vda2", "/sys/devices/platform/block/vda/vda2"),
259 FakeFs::BackingDevice(file.path(), "/sys/devices/platform/block/vda/vda2"),
260 ]);
261
262 assert_eq!(
263 find_backing_block_device(file.path(), fake_fs.path().join("sys").to_str().unwrap())
264 .unwrap(),
265 "vda"
266 );
267 }
268
269 #[test]
configure_block_device_queue_depth()270 fn configure_block_device_queue_depth() {
271 let file = tempfile::NamedTempFile::new().unwrap();
272 let fake_fs = FakeFs::build(&[
273 FakeFs::File("/sys/devices/platform/block/vda/mq/0/nr_tags", "31"),
274 FakeFs::File("/sys/devices/virtual/block/loop97/queue/nr_requests", "128"),
275 FakeFs::Symlink("/sys/class/block/vda", "/sys/devices/platform/block/vda"),
276 FakeFs::Symlink("/sys/class/block/loop97", "/sys/devices/virtual/block/loop97"),
277 FakeFs::BackingDevice(file.path(), "/sys/devices/platform/block/vda/"),
278 ]);
279
280 configure_block_device_queue_depth_with_sysfs(
281 "loop97",
282 file.path(),
283 fake_fs.path().join("sys").to_str().unwrap(),
284 )
285 .unwrap();
286
287 assert_eq!(
288 fs::read_to_string(fake_fs.path().join("sys/class/block/loop97/queue/nr_requests"))
289 .unwrap(),
290 "31"
291 );
292 }
293
294 #[test]
configure_block_device_queue_depth_error()295 fn configure_block_device_queue_depth_error() {
296 let file = tempfile::NamedTempFile::new().unwrap();
297 let fake_fs = FakeFs::build(&[
298 FakeFs::Dir("/sys/devices/platform/block/vda/"),
299 FakeFs::File("/sys/devices/virtual/block/loop97/queue/nr_requests", "128"),
300 FakeFs::Symlink("/sys/class/block/vda", "/sys/devices/platform/block/vda"),
301 FakeFs::Symlink("/sys/class/block/loop97", "/sys/devices/virtual/block/loop97"),
302 FakeFs::BackingDevice(file.path(), "/sys/devices/platform/block/vda/"),
303 ]);
304
305 assert!(matches!(
306 configure_block_device_queue_depth_with_sysfs(
307 "loop97",
308 file.path(),
309 fake_fs.path().join("sys").to_str().unwrap()
310 ),
311 Err(BlockDeviceError::DeviceFileIo(_))
312 ));
313 }
314 }
315