• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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