Skip to main content

starnix_modules_device_mapper/
lib.rs

1// Copyright 2024 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#![recursion_limit = "512"]
6
7use bitflags::bitflags;
8use dm_verity::{
9    DmVerityError, DmVerityTargetOptionalParams, DmVerityTargetParams, HashAlgorithm,
10    MerkleVerifier,
11};
12use linux_uapi::DM_UUID_LEN;
13use mundane::hash::{Digest, Hasher, Sha256, Sha512};
14use starnix_core::device::DeviceMode;
15use starnix_core::device::kobject::{Device, DeviceMetadata};
16use starnix_core::fs::sysfs::{BlockDeviceInfo, build_block_device_directory, get_sysfs};
17use starnix_core::mm::memory::MemoryObject;
18use starnix_core::mm::{MemoryAccessor, MemoryAccessorExt, ProtectionFlags};
19use starnix_core::security;
20use starnix_core::task::{CurrentTask, Kernel};
21use starnix_core::vfs::buffers::{InputBuffer, VecOutputBuffer};
22use starnix_core::vfs::pseudo::stub_empty_file::StubEmptyFile;
23use starnix_core::vfs::{
24    FileHandle, FileObject, FileObjectState, FileOps, FileToken, FsString, NamespaceNode,
25    OutputBuffer, fileops_impl_dataless, fileops_impl_noop_sync, fileops_impl_seekable,
26    fileops_impl_seekless,
27};
28use starnix_ext::map_ext::EntryExt;
29use starnix_logging::{bug_ref, log_trace, track_stub};
30use starnix_sync::{DeviceMapperRegistryDevicesLock, DmDeviceStateLock, LockDepMutex};
31use starnix_syscalls::{SUCCESS, SyscallArg, SyscallResult};
32use starnix_uapi::auth::CAP_SYS_ADMIN;
33use starnix_uapi::device_id::{DEVICE_MAPPER_MAJOR, DeviceId, LOOP_MAJOR};
34use starnix_uapi::errors::Errno;
35use starnix_uapi::file_mode::mode;
36use starnix_uapi::open_flags::OpenFlags;
37use starnix_uapi::user_address::{UserCString, UserRef};
38use starnix_uapi::{
39    DM_ACTIVE_PRESENT_FLAG, DM_BUFFER_FULL_FLAG, DM_DEV_ARM_POLL, DM_DEV_CREATE, DM_DEV_REMOVE,
40    DM_DEV_RENAME, DM_DEV_SET_GEOMETRY, DM_DEV_STATUS, DM_DEV_SUSPEND, DM_DEV_WAIT,
41    DM_GET_TARGET_VERSION, DM_IMA_MEASUREMENT_FLAG, DM_INACTIVE_PRESENT_FLAG, DM_LIST_DEVICES,
42    DM_LIST_VERSIONS, DM_MAX_TYPE_NAME, DM_NAME_LEN, DM_NAME_LIST_FLAG_DOESNT_HAVE_UUID,
43    DM_NAME_LIST_FLAG_HAS_UUID, DM_READONLY_FLAG, DM_REMOVE_ALL, DM_STATUS_TABLE_FLAG,
44    DM_SUSPEND_FLAG, DM_TABLE_CLEAR, DM_TABLE_DEPS, DM_TABLE_LOAD, DM_TABLE_STATUS, DM_TARGET_MSG,
45    DM_UEVENT_GENERATED_FLAG, DM_VERSION, DM_VERSION_MAJOR, DM_VERSION_MINOR,
46    DM_VERSION_PATCHLEVEL, errno, error, uapi,
47};
48use std::collections::btree_map::{BTreeMap, Entry};
49use std::ops::Sub;
50use std::sync::Arc;
51
52const SECTOR_SIZE: u64 = 512;
53// The value of the data_size field in the output dm_ioctl struct when no data is returned as per
54// Linux 6.6.15.
55const DATA_SIZE: u32 = 305;
56// Observed version values for the dm-verity target as per Linux 6.6.15.
57const DM_VERITY_VERSION_MAJOR: u32 = 1;
58const DM_VERITY_VERSION_MINOR: u32 = 9;
59const DM_VERITY_VERSION_PATCHLEVEL: u32 = 0;
60
61bitflags! {
62    #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
63    struct DeviceMapperFlags: u32 {
64        const ACTIVE_PRESENT = DM_ACTIVE_PRESENT_FLAG;
65        const INACTIVE_PRESENT = DM_INACTIVE_PRESENT_FLAG;
66        const READONLY = DM_READONLY_FLAG;
67        const SUSPEND = DM_SUSPEND_FLAG;
68        const UEVENT_GENERATED = DM_UEVENT_GENERATED_FLAG;
69        const BUFFER_FULL = DM_BUFFER_FULL_FLAG;
70        const STATUS_TABLE = DM_STATUS_TABLE_FLAG;
71        const IMA_MEASUREMENT = DM_IMA_MEASUREMENT_FLAG;
72        const DM_NAME_LIST_HAS_UUID = DM_NAME_LIST_FLAG_HAS_UUID;
73        const DM_NAME_LIST_NO_UUID = DM_NAME_LIST_FLAG_DOESNT_HAVE_UUID;
74    }
75}
76
77pub fn device_mapper_init(kernel: &Kernel) -> Result<(), Errno> {
78    kernel.device_registry.register_major(
79        "device-mapper".into(),
80        DeviceMode::Block,
81        DEVICE_MAPPER_MAJOR,
82        get_or_create_dm_device,
83    )?;
84
85    let dir_mode = 0o755;
86    kernel.device_registry.objects.root.edit(&get_sysfs(kernel), |root| {
87        root.subdir("module", dir_mode, |dir| {
88            dir.subdir("dm_bufio", dir_mode, |dir| {
89                dir.subdir("parameters", dir_mode, |dir| {
90                    dir.entry(
91                        "max_age_seconds",
92                        StubEmptyFile::new_node(bug_ref!("https://fxbug.dev/452096300")),
93                        mode!(IFREG, 0o644),
94                    );
95                });
96            });
97            dir.subdir("dm_verity", dir_mode, |dir| {
98                dir.subdir("parameters", dir_mode, |dir| {
99                    dir.entry(
100                        "prefetch_cluster",
101                        StubEmptyFile::new_node(bug_ref!("https://fxbug.dev/322893670")),
102                        mode!(IFREG, 0o644),
103                    );
104                });
105            });
106        });
107    });
108
109    Ok(())
110}
111
112#[derive(Debug, Default)]
113pub struct DeviceMapperRegistry {
114    devices: LockDepMutex<BTreeMap<u32, Arc<DmDevice>>, DeviceMapperRegistryDevicesLock>,
115}
116
117impl DeviceMapperRegistry {
118    /// Looks up a dm-device based on strictly one of the following: name, uuid, or dev number.
119    fn get(&self, io: &uapi::dm_ioctl) -> Result<Arc<DmDevice>, Errno> {
120        if io.name != [0; DM_NAME_LEN as usize] {
121            if io.uuid != [0; DM_UUID_LEN as usize] || io.dev > 0 {
122                return error!(EINVAL);
123            } else {
124                return self.get_by_name(io.name);
125            }
126        }
127        if io.uuid != [0; DM_UUID_LEN as usize] {
128            if io.dev > 0 {
129                return error!(EINVAL);
130            } else {
131                return self.get_by_uuid(io.uuid);
132            }
133        }
134        let dev_minor = ((io.dev >> 12 & 0xffffff00) | (io.dev & 0xff)) as u32;
135        self.devices.lock().get(&dev_minor).ok_or_else(|| errno!(ENODEV)).cloned()
136    }
137
138    fn get_by_name(
139        &self,
140        name: [std::ffi::c_char; DM_NAME_LEN as usize],
141    ) -> Result<Arc<DmDevice>, Errno> {
142        let devices = self.devices.lock();
143        let entry = devices.iter().find(|(_, device)| {
144            let state = device.state.lock();
145            state.name == name
146        });
147        if let Some((_, device)) = entry { Ok(device.clone()) } else { error!(ENODEV) }
148    }
149
150    fn get_by_uuid(
151        &self,
152        uuid: [std::ffi::c_char; DM_UUID_LEN as usize],
153    ) -> Result<Arc<DmDevice>, Errno> {
154        let devices = self.devices.lock();
155        let entry = devices.iter().find(|(_, device)| {
156            let state = device.state.lock();
157            state.uuid == uuid
158        });
159        if let Some((_, device)) = entry { Ok(device.clone()) } else { error!(ENODEV) }
160    }
161
162    fn get_or_create_by_minor(
163        &self,
164        current_task: &CurrentTask,
165        minor: u32,
166    ) -> Result<Arc<DmDevice>, Errno> {
167        self.devices
168            .lock()
169            .entry(minor)
170            .or_insert_with_fallible(|| DmDevice::new(current_task, minor))
171            .cloned()
172    }
173
174    /// Finds a free minor number in the DeviceMapperRegistry. Returns that minor number along with
175    /// a new DmDevice associated with that minor number.
176    fn find(&self, current_task: &CurrentTask) -> Result<Arc<DmDevice>, Errno> {
177        let mut devices = self.devices.lock();
178        for minor in 0..u32::MAX {
179            match devices.entry(minor) {
180                Entry::Vacant(e) => {
181                    let device = DmDevice::new(current_task, minor)?;
182                    e.insert(device.clone());
183                    return Ok(device);
184                }
185                Entry::Occupied(_) => {}
186            }
187        }
188        error!(ENODEV)
189    }
190
191    /// Removes `device` from both the Device and DeviceMapper registries.
192    fn remove(
193        &self,
194        current_task: &CurrentTask,
195        devices: &mut BTreeMap<u32, Arc<DmDevice>>,
196        minor: u32,
197        k_device: &Option<Device>,
198    ) -> Result<(), Errno> {
199        devices.remove(&minor).ok_or_else(|| errno!(ENODEV))?;
200        let kernel = current_task.kernel();
201        let registry = &kernel.device_registry;
202        if let Some(dev) = &k_device {
203            registry.remove_device(current_task, dev.clone());
204        } else {
205            return error!(EINVAL);
206        }
207        Ok(())
208    }
209}
210#[derive(Debug, Default)]
211pub struct DmDevice {
212    number: DeviceId,
213    state: LockDepMutex<DmDeviceState, DmDeviceStateLock>,
214}
215
216impl DmDevice {
217    fn new(current_task: &CurrentTask, minor: u32) -> Result<Arc<Self>, Errno> {
218        let kernel = current_task.kernel();
219        let registry = &kernel.device_registry;
220        let dm_device_name = FsString::from(format!("dm-{minor}"));
221        let block_class = registry.objects.block_class();
222        let device = Arc::new(Self {
223            number: DeviceId::new(DEVICE_MAPPER_MAJOR, minor),
224            ..Default::default()
225        });
226        let device_weak = Arc::<DmDevice>::downgrade(&device);
227        let k_device = registry.add_major_device(
228            current_task.kernel(),
229            dm_device_name.as_ref(),
230            DeviceMetadata::new(
231                dm_device_name.clone(),
232                DeviceId::new(DEVICE_MAPPER_MAJOR, minor),
233                DeviceMode::Block,
234            )
235            .with_devtype("disk"),
236            /* parent = */ None,
237            block_class,
238            |device, dir| build_block_device_directory(device, device_weak, dir),
239        );
240        {
241            let mut state = device.state.lock();
242            state.set_k_device(k_device);
243        }
244        Ok(device)
245    }
246
247    fn create_file_ops(self: &Arc<Self>) -> Box<dyn FileOps> {
248        let mut state = self.state.lock();
249        state.open_count += 1;
250        Box::new(DmDeviceFile { device: self.clone() })
251    }
252}
253
254impl BlockDeviceInfo for DmDevice {
255    fn size(&self) -> Result<usize, Errno> {
256        let state = self.state.lock();
257        if !state.suspended {
258            if let Some(active_table) = &state.active_table {
259                Ok(active_table.size())
260            } else {
261                Ok(0)
262            }
263        } else {
264            Ok(0)
265        }
266    }
267}
268struct DmDeviceFile {
269    device: Arc<DmDevice>,
270}
271
272impl FileOps for DmDeviceFile {
273    fileops_impl_seekable!();
274
275    // Writes aren't supported for these files, no need to sync the data.
276    fileops_impl_noop_sync!();
277
278    fn write(
279        &self,
280        _file: &FileObject,
281        _current_task: &CurrentTask,
282        _offset: usize,
283        _data: &mut dyn InputBuffer,
284    ) -> Result<usize, Errno> {
285        error!(ENOTSUP)
286    }
287
288    fn read(
289        &self,
290        _file: &FileObject,
291        current_task: &CurrentTask,
292        offset: usize,
293        data: &mut dyn OutputBuffer,
294    ) -> Result<usize, Errno> {
295        let device = &self.device;
296        let mut state = device.state.lock();
297        if state.suspended {
298            track_stub!(TODO("https://fxbug.dev/338241090"), "Defer io for suspended devices.");
299            return Ok(0);
300        }
301        if let Some(active_table) = &mut state.active_table {
302            let mut bytes_read = 0;
303            let to_read = std::cmp::min(
304                data.available(),
305                active_table.size().checked_sub(offset).ok_or_else(|| errno!(EINVAL))?,
306            );
307            let mut buffer = VecOutputBuffer::new(to_read);
308            if active_table.targets.is_empty() {
309                return error!(EINVAL);
310            }
311            if active_table.targets.len() > 1 {
312                track_stub!(
313                    TODO("https://fxbug.dev/339701082"),
314                    "Support reads for multiple targets."
315                );
316                return error!(ENOTSUP);
317            }
318            let target = &mut active_table.targets[0];
319            let start = (target.sector_start * SECTOR_SIZE) as usize;
320            debug_assert!(start == 0);
321            let size = (target.length * SECTOR_SIZE) as usize;
322            if offset >= start && offset < start + size {
323                match &mut target.target_type {
324                    TargetType::Verity(verity_target) => {
325                        if to_read % verity_target.params.hash_block_size as usize != 0 {
326                            return error!(EINVAL);
327                        }
328                        let read = verity_target.block_device.ops().read(
329                            &verity_target.block_device,
330                            current_task,
331                            offset - start,
332                            &mut buffer,
333                        )?;
334                        bytes_read += read;
335                        if let Err(_) = verity_target.verifier.verify(offset - start, buffer.data())
336                        {
337                            verity_target.corrupted = true;
338                            return error!(EINVAL);
339                        }
340                    }
341                    TargetType::Error => {
342                        return error!(EIO);
343                    }
344                }
345            }
346            let read = data.write_all(buffer.data())?;
347            debug_assert!(read == bytes_read);
348            Ok(bytes_read)
349        } else {
350            Ok(0)
351        }
352    }
353
354    fn get_memory(
355        &self,
356        _file: &FileObject,
357        current_task: &CurrentTask,
358        length: Option<usize>,
359        prot: ProtectionFlags,
360    ) -> Result<Arc<MemoryObject>, Errno> {
361        let device = &self.device;
362        let state = device.state.lock();
363        if state.suspended {
364            track_stub!(TODO("https://fxbug.dev/338241090"), "Defer io for suspended devices.");
365            return error!(EINVAL);
366        }
367        if let Some(active_table) = &state.active_table {
368            if active_table.targets.is_empty() {
369                return error!(EINVAL);
370            }
371            if active_table.targets.len() > 1 {
372                track_stub!(
373                    TODO("https://fxbug.dev/339701082"),
374                    "Support pager-backed vmos for multiple targets."
375                );
376                return error!(ENOTSUP);
377            }
378            match &active_table.targets[0].target_type {
379                TargetType::Verity(verity_target) => verity_target.block_device.ops().get_memory(
380                    &verity_target.block_device,
381                    current_task,
382                    length,
383                    prot,
384                ),
385                TargetType::Error => {
386                    error!(EIO)
387                }
388            }
389        } else {
390            error!(EINVAL)
391        }
392    }
393
394    fn get_file_token(
395        &self,
396        _file: &FileObject,
397        current_task: &CurrentTask,
398    ) -> Result<FileToken, Errno> {
399        let state = self.device.state.lock();
400        if state.suspended {
401            return error!(EINVAL);
402        }
403        if let Some(active_table) = &state.active_table {
404            if active_table.targets.is_empty() {
405                return error!(EINVAL);
406            }
407            if active_table.targets.len() > 1 {
408                return error!(ENOTSUP);
409            }
410            match &active_table.targets[0].target_type {
411                TargetType::Verity(verity_target) => {
412                    // `get_file_token` only succeeds when `block_device` is backed by a Fuchsia
413                    // package blob (`RemoteBundle` / `VmoBlob` in `pkg-cache`), which is already
414                    // cryptographically verified at page-in time against the Fuchsia blob's Merkle
415                    // tree by `BlobPagerAndVerifier` before any child VMO pages are supplied.
416                    // Furthermore, `VerityTarget::create_and_verify` has already verified the
417                    // dm-verity root digest and leaf hashes during `DM_TABLE_LOAD`.
418                    verity_target.block_device.get_file_token(current_task)
419                }
420                TargetType::Error => error!(EIO),
421            }
422        } else {
423            error!(EINVAL)
424        }
425    }
426
427    fn ioctl(
428        &self,
429        _file: &FileObject,
430        current_task: &CurrentTask,
431        request: u32,
432        arg: SyscallArg,
433    ) -> Result<SyscallResult, Errno> {
434        let device = &self.device;
435        let mut state = device.state.lock();
436        if !state.suspended {
437            if let Some(active_table) = &mut state.active_table {
438                if active_table.targets.len() == 1 {
439                    let target = &mut active_table.targets[0];
440                    if let TargetType::Verity(args) = &mut target.target_type {
441                        return args.block_device.ops().ioctl(
442                            &*args.block_device,
443                            current_task,
444                            request,
445                            arg,
446                        );
447                    }
448                }
449            }
450        }
451
452        error!(ENOTTY)
453    }
454
455    fn close(self: Box<Self>, _file: &FileObjectState, _current_task: &CurrentTask) {
456        let mut state = self.device.state.lock();
457        state.open_count -= 1;
458    }
459}
460#[derive(Debug)]
461struct DmDeviceState {
462    version: [u32; 3],
463    target_count: u32,
464    open_count: u64,
465    name: [std::ffi::c_char; 128],
466    uuid: [std::ffi::c_char; 129],
467    active_table: Option<DmDeviceTable>,
468    inactive_table: Option<DmDeviceTable>,
469    flags: DeviceMapperFlags,
470    suspended: bool,
471    k_device: Option<Device>,
472}
473
474impl Default for DmDeviceState {
475    fn default() -> Self {
476        DmDeviceState {
477            version: [0; 3],
478            name: [0 as std::ffi::c_char; 128],
479            uuid: [0 as std::ffi::c_char; 129],
480            target_count: 0,
481            open_count: 0,
482            active_table: None,
483            inactive_table: None,
484            flags: DeviceMapperFlags::empty(),
485            suspended: false,
486            k_device: None,
487        }
488    }
489}
490
491impl DmDeviceState {
492    fn set_version(&mut self) {
493        self.version = [DM_VERSION_MAJOR, DM_VERSION_MINOR, DM_VERSION_PATCHLEVEL];
494    }
495
496    fn set_name(&mut self, name: [std::ffi::c_char; 128]) {
497        self.name = name;
498    }
499
500    fn set_uuid(&mut self, uuid: [std::ffi::c_char; 129]) {
501        self.uuid = uuid;
502    }
503
504    fn set_inactive_table(&mut self, inactive_table: DmDeviceTable) {
505        self.inactive_table = Some(inactive_table);
506    }
507
508    fn resume(&mut self) {
509        if let Some(inactive_table) = self.inactive_table.take() {
510            self.active_table = Some(inactive_table);
511        }
512        self.suspended = false;
513    }
514
515    fn remove(&mut self) {
516        self.active_table.take();
517        self.suspended = false;
518    }
519
520    fn set_target_count(&mut self, target_count: u32) {
521        self.target_count = target_count;
522    }
523
524    fn get_target_count(&self) -> u32 {
525        if let Some(_) = self.active_table { self.target_count } else { 0 }
526    }
527
528    fn add_flags(&mut self, flags: DeviceMapperFlags) {
529        self.flags |= flags;
530    }
531
532    fn get_flags(&self) -> DeviceMapperFlags {
533        let mut flags = DeviceMapperFlags::empty();
534        if let Some(active_table) = &self.active_table {
535            flags |= DeviceMapperFlags::ACTIVE_PRESENT;
536            if active_table.readonly {
537                flags |= DeviceMapperFlags::READONLY;
538            }
539        }
540        if let Some(_) = &self.inactive_table {
541            flags |= DeviceMapperFlags::INACTIVE_PRESENT;
542        }
543        if self.suspended {
544            flags |= DeviceMapperFlags::SUSPEND;
545        }
546        flags
547    }
548
549    fn suspend(&mut self) {
550        self.suspended = true;
551    }
552
553    fn set_k_device(&mut self, k_device: Device) {
554        self.k_device = Some(k_device);
555    }
556}
557#[derive(Debug, Clone)]
558struct DmDeviceTarget {
559    sector_start: u64,
560    length: u64,
561    status: i32,
562    name: [std::ffi::c_char; DM_MAX_TYPE_NAME as usize],
563    target_type: TargetType,
564}
565
566#[derive(Debug, Default, Clone)]
567pub struct DmDeviceTable {
568    targets: Vec<DmDeviceTarget>,
569    readonly: bool,
570}
571
572impl DmDeviceTable {
573    fn size(&self) -> usize {
574        let mut size = 0;
575        for target in &self.targets {
576            size += (SECTOR_SIZE * target.length) as usize;
577        }
578        size
579    }
580}
581
582struct DeviceMapper {
583    registry: Arc<DeviceMapperRegistry>,
584}
585
586impl DeviceMapper {
587    pub fn new(registry: Arc<DeviceMapperRegistry>) -> Self {
588        Self { registry: registry }
589    }
590}
591
592#[derive(Debug, Clone)]
593enum TargetType {
594    Verity(Box<VerityTarget>),
595    Error,
596}
597
598#[derive(Debug, Clone)]
599struct VerityTarget {
600    verifier: MerkleVerifier,
601    params: DmVerityTargetParams,
602    block_device: FileHandle,
603    corrupted: bool,
604}
605
606impl VerityTarget {
607    fn create_and_verify(
608        params: DmVerityTargetParams,
609        block_device: FileHandle,
610        leaf_hashes: Box<[u8]>,
611    ) -> Result<Self, Errno> {
612        let verifier = dm_verity::create_verifier(&params, leaf_hashes).map_err(|e| match &e {
613            DmVerityError::RootHashMismatch => {
614                starnix_logging::log_warn!(
615                    "Merkle tree root hash mismatch. Expected: {}",
616                    params.root_digest
617                );
618                errno!(EINVAL)
619            }
620            DmVerityError::UnsupportedAlgorithm(algorithm) => {
621                starnix_logging::log_warn!(
622                    "dm-verity verifier creation failed for unsupported algorithm: {}",
623                    algorithm
624                );
625                errno!(ENOTSUP)
626            }
627            _ => {
628                starnix_logging::log_warn!("dm-verity verifier creation failed: {:?}", e);
629                errno!(EINVAL)
630            }
631        })?;
632        Ok(Self { verifier, params, block_device, corrupted: false })
633    }
634
635    fn parameter_string(&self) -> String {
636        let params = &self.params;
637        let base_string = format!(
638            "{} {} {} {} {} {} {} {} {} {}",
639            params.version,
640            params.block_device_path,
641            params.hash_device_path,
642            params.data_block_size,
643            params.hash_block_size,
644            params.num_data_blocks,
645            params.hash_start_block,
646            params.hash_algorithm.as_str(),
647            params.root_digest,
648            params.salt
649        );
650        let mut optional_arg_count = 0;
651        let mut optional_string = String::new();
652        if params.optional_params.ignore_zero_blocks {
653            optional_arg_count += 1;
654            optional_string.push_str(" ignore_zero_blocks");
655        }
656        if params.optional_params.restart_on_corruption {
657            optional_arg_count += 1;
658            optional_string.push_str(" restart on corruption");
659        }
660        if optional_arg_count > 0 {
661            format!("{base_string} {optional_arg_count}{optional_string}")
662        } else {
663            base_string
664        }
665    }
666}
667
668// Returns the FileHandle and minor number of the device found at `device path` formatted as
669// either /dev/loop# of MAJOR:MINOR
670
671fn open_device(
672    current_task: &CurrentTask,
673    device_path: String,
674) -> Result<(u64, FileHandle), Errno> {
675    let device_path_vec: Vec<&str> = device_path.split(":").collect();
676    if device_path_vec.len() == 1 {
677        let dev = current_task.open_file(device_path.as_str().into(), OpenFlags::RDONLY)?;
678        let loop_device_vec: Vec<&str> = device_path.split("loop").collect();
679        let minor = loop_device_vec[1].parse::<u64>().unwrap();
680        Ok((minor, dev))
681    } else {
682        let minor = device_path_vec[1].parse::<u64>().unwrap();
683        let dev = current_task
684            .open_file(format!("/dev/loop{minor}").as_str().into(), OpenFlags::RDONLY)?;
685        Ok((minor, dev))
686    }
687}
688
689fn size_of_merkle_tree_preceding_leaf_nodes(
690    leaf_nodes_size: u64,
691    hash_size: u64,
692    hash_block_size: u64,
693) -> u64 {
694    let mut total_size = 0;
695    let mut data_size = leaf_nodes_size;
696    while data_size > hash_block_size {
697        let num_hashes = data_size.div_ceil(hash_block_size);
698        let hashes_per_block = hash_block_size.div_ceil(hash_size);
699        let hash_blocks = num_hashes.div_ceil(hashes_per_block);
700        data_size = hash_blocks * hash_block_size;
701        total_size += data_size;
702    }
703    total_size
704}
705
706// Parse the parameter string into a TargetType.
707
708fn parse_parameter_string(
709    current_task: &CurrentTask,
710    target_type: &str,
711    parameter_str: String,
712) -> Result<TargetType, Errno> {
713    match target_type {
714        "verity" => {
715            let v: Vec<&str> = parameter_str.split(" ").collect();
716            let hash_algorithm = v[7].parse::<HashAlgorithm>().map_err(|_| errno!(ENOTSUP))?;
717            let mut params = DmVerityTargetParams {
718                version: String::from(v[0]),
719                block_device_path: String::from(v[1]),
720                hash_device_path: String::from(v[2]),
721                data_block_size: v[3].parse::<u64>().unwrap(),
722                hash_block_size: v[4].parse::<u64>().unwrap(),
723                num_data_blocks: v[5].parse::<u64>().unwrap(),
724                hash_start_block: v[6].parse::<u64>().unwrap(),
725                hash_algorithm,
726                root_digest: String::from(v[8]),
727                salt: String::from(v[9]),
728                optional_params: DmVerityTargetOptionalParams::default(),
729            };
730
731            if v.len() > 10 {
732                let num_optional_args = v[10].parse::<u64>().unwrap();
733                if num_optional_args > 2 {
734                    return error!(ENOTSUP);
735                }
736                for i in 0..num_optional_args {
737                    if v[11 + i as usize] == "ignore_zero_blocks" {
738                        params.optional_params.ignore_zero_blocks = true;
739                    } else if v[11 + i as usize] == "restart_on_corruption" {
740                        track_stub!(
741                            TODO("https://fxbug.dev/338243823"),
742                            "Support restart on corruption."
743                        );
744                        params.optional_params.restart_on_corruption = true;
745                    } else {
746                        return error!(ENOTSUP);
747                    }
748                }
749            }
750
751            let (minor, block_device) = open_device(current_task, params.block_device_path)?;
752            params.block_device_path = format!("{LOOP_MAJOR}:{minor}");
753
754            let (minor, hash_device) = if params.hash_device_path == params.block_device_path {
755                (minor, block_device.clone())
756            } else {
757                open_device(current_task, params.hash_device_path)?
758            };
759            params.hash_device_path = format!("{LOOP_MAJOR}:{minor}");
760
761            let hash_size: u64 = match params.hash_algorithm {
762                HashAlgorithm::Sha256 => <Sha256 as Hasher>::Digest::DIGEST_LEN as u64,
763                HashAlgorithm::Sha512 => <Sha512 as Hasher>::Digest::DIGEST_LEN as u64,
764            };
765
766            debug_assert!(params.hash_block_size > 0);
767            let data_size = params.num_data_blocks * params.data_block_size;
768            let num_hashes = data_size.div_ceil(params.hash_block_size);
769            let hashes_per_block = params.hash_block_size.div_ceil(hash_size);
770            let hash_blocks = num_hashes.div_ceil(hashes_per_block);
771            let leaf_nodes_size = hash_blocks * params.hash_block_size;
772            let mut buffer = VecOutputBuffer::new(leaf_nodes_size as usize);
773            let offset = params.hash_start_block * params.hash_block_size
774                + size_of_merkle_tree_preceding_leaf_nodes(
775                    leaf_nodes_size,
776                    hash_size,
777                    params.hash_block_size,
778                );
779            let bytes_read =
780                hash_device.ops().read(&hash_device, current_task, offset as usize, &mut buffer)?;
781            debug_assert!(bytes_read == leaf_nodes_size as usize);
782
783            let leaf_hashes: Vec<u8> = buffer.into();
784            let verity_target = VerityTarget::create_and_verify(
785                params,
786                block_device,
787                leaf_hashes.into_boxed_slice(),
788            )?;
789            Ok(TargetType::Verity(Box::new(verity_target)))
790        }
791        "error" => Ok(TargetType::Error),
792        _ => error!(ENOTSUP),
793    }
794}
795
796fn check_version_compatibility(major: u32, minor: u32) -> Result<(), Errno> {
797    // The version field of the input dm-ioctl struct should represent the version of the interface
798    // that the client was compiled with. The major number must match the kernel's, the minor
799    // number is backwards compatible, and the patchlevel is forwards and backwards compatible.
800    if major != DM_VERSION_MAJOR || minor > DM_VERSION_MINOR {
801        return error!(EINVAL);
802    }
803    return Ok(());
804}
805
806impl FileOps for DeviceMapper {
807    fileops_impl_seekless!();
808    fileops_impl_dataless!();
809    fileops_impl_noop_sync!();
810
811    fn ioctl(
812        &self,
813        _file: &FileObject,
814        current_task: &CurrentTask,
815        request: u32,
816        arg: SyscallArg,
817    ) -> Result<SyscallResult, Errno> {
818        security::check_task_capable(current_task, CAP_SYS_ADMIN).map_err(|_| errno!(EACCES))?;
819        let user_info = UserRef::<uapi::dm_ioctl>::from(arg);
820        let info_addr: starnix_uapi::user_address::UserAddress = user_info.addr();
821        let info = current_task.read_object(user_info)?;
822        let flags = DeviceMapperFlags::from_bits_truncate(info.flags);
823        match request {
824            DM_DEV_CREATE => {
825                // Expect name and version to be set. This should not fail if uuid is not set.
826                if info.name == [0; DM_NAME_LEN as usize] || info.version == [0; 3] {
827                    return error!(EINVAL);
828                }
829                let dm_device = self.registry.find(current_task)?;
830                let mut state = dm_device.state.lock();
831                check_version_compatibility(info.version[0], info.version[1])?;
832                state.set_version();
833                state.set_name(info.name);
834                state.set_uuid(info.uuid);
835                let i = uapi::dm_ioctl {
836                    name: state.name,
837                    version: state.version,
838                    uuid: state.uuid,
839                    dev: dm_device.number.bits(),
840                    data_size: DATA_SIZE,
841                    data_start: 0,
842                    ..Default::default()
843                };
844                log_trace!("DM_DEV_CREATE returned dm_ioctl: {:?}", i);
845                current_task.write_object(user_info, &i)?;
846                Ok(SUCCESS)
847            }
848            DM_TABLE_LOAD => {
849                let mut start_addr = (info_addr + info.data_start)?;
850                let mut num_targets = 0;
851                let dm_device = self.registry.get(&info)?;
852                let mut state = dm_device.state.lock();
853                let mut table = DmDeviceTable { ..Default::default() };
854                if flags.contains(DeviceMapperFlags::READONLY) {
855                    table.readonly = true;
856                    state.add_flags(DeviceMapperFlags::READONLY);
857                }
858                if info.target_count > 1 {
859                    track_stub!(TODO("https://fxbug.dev/339701082"), "Support multiple targets.");
860                    return error!(ENOTSUP);
861                }
862                track_stub!(
863                    TODO("https://fxbug.dev/338245544"),
864                    "Make sure targets are contiguous and non-overlapping"
865                );
866                while num_targets < info.target_count {
867                    let target_ref = UserRef::<uapi::dm_target_spec>::new(start_addr);
868                    let target = current_task.read_object(target_ref)?;
869                    let parameter_cstring =
870                        UserCString::new(current_task, target_ref.next()?.addr());
871                    let parameters = current_task.read_c_string_to_vec(
872                        parameter_cstring,
873                        target.next as usize - std::mem::size_of::<uapi::dm_target_spec>(),
874                    )?;
875                    let target_type_addr = start_addr
876                        .checked_add(
877                            2 * std::mem::size_of::<u64>()
878                                + std::mem::size_of::<u32>()
879                                + std::mem::size_of::<i32>(),
880                        )
881                        .ok_or_else(|| errno!(EINVAL))?;
882                    let target_type_cstring = UserCString::new(current_task, target_type_addr);
883                    let target_type = current_task
884                        .read_c_string_to_vec(target_type_cstring, DM_MAX_TYPE_NAME as usize)?;
885
886                    let device_target = DmDeviceTarget {
887                        sector_start: target.sector_start,
888                        length: target.length,
889                        status: target.status,
890                        name: target.target_type,
891                        target_type: parse_parameter_string(
892                            current_task,
893                            &target_type.to_string(),
894                            parameters.to_string(),
895                        )?,
896                    };
897                    table.targets.push(device_target);
898                    num_targets += 1;
899                    debug_assert!(target.next % 8 == 0);
900                    start_addr = start_addr
901                        .checked_add(target.next as usize)
902                        .ok_or_else(|| errno!(EINVAL))?;
903                }
904
905                // Update the metadata of the dm device
906                state.set_inactive_table(table);
907                state.set_target_count(num_targets);
908                let i = uapi::dm_ioctl {
909                    name: state.name,
910                    version: state.version,
911                    uuid: state.uuid,
912                    dev: dm_device.number.bits(),
913                    data_size: DATA_SIZE,
914                    data_start: 0,
915                    flags: state.get_flags().bits(),
916                    target_count: state.get_target_count(),
917                    ..Default::default()
918                };
919                log_trace!("DM_TABLE_LOAD returned dm_ioctl: {:?}", i);
920                current_task.write_object(user_info, &i)?;
921                Ok(SUCCESS)
922            }
923            DM_DEV_SUSPEND => {
924                let dm_device = self.registry.get(&info)?;
925                let mut state = dm_device.state.lock();
926                if flags.contains(DeviceMapperFlags::SUSPEND) {
927                    state.suspend();
928                } else {
929                    state.resume();
930                }
931                let mut out_flags = state.get_flags();
932                if !flags.contains(DeviceMapperFlags::SUSPEND) {
933                    out_flags |= DeviceMapperFlags::UEVENT_GENERATED;
934                }
935                let i = uapi::dm_ioctl {
936                    name: state.name,
937                    version: state.version,
938                    uuid: state.uuid,
939                    dev: dm_device.number.bits(),
940                    data_size: DATA_SIZE,
941                    data_start: 0,
942                    flags: out_flags.bits(),
943                    target_count: state.get_target_count(),
944                    ..Default::default()
945                };
946                log_trace!("DM_DEV_SUSPEND returned dm_ioctl: {:?}", i);
947                current_task.write_object(user_info, &i)?;
948                Ok(SUCCESS)
949            }
950            DM_DEV_STATUS => {
951                let dm_device = self.registry.get(&info)?;
952                let state = dm_device.state.lock();
953                let i = uapi::dm_ioctl {
954                    name: state.name,
955                    version: state.version,
956                    uuid: state.uuid,
957                    dev: dm_device.number.bits(),
958                    data_size: DATA_SIZE,
959                    data_start: 0,
960                    flags: state.get_flags().bits(),
961                    target_count: state.get_target_count(),
962                    ..Default::default()
963                };
964                log_trace!("DM_DEV_STATUS returned dm_ioctl: {:?}", i);
965                current_task.write_object(user_info, &i)?;
966                Ok(SUCCESS)
967            }
968            DM_DEV_REMOVE => {
969                let dm_device = self.registry.get(&info)?;
970                let mut devices = self.registry.devices.lock();
971                let mut state = dm_device.state.lock();
972                if state.open_count > 0 {
973                    return error!(ENOTSUP);
974                }
975                self.registry.remove(
976                    current_task,
977                    &mut devices,
978                    dm_device.number.minor(),
979                    &state.k_device,
980                )?;
981                state.remove();
982                let i = uapi::dm_ioctl {
983                    name: state.name,
984                    version: state.version,
985                    uuid: state.uuid,
986                    dev: dm_device.number.bits(),
987                    data_size: DATA_SIZE,
988                    data_start: 0,
989                    flags: (state.get_flags() | DeviceMapperFlags::UEVENT_GENERATED).bits(),
990                    target_count: state.get_target_count(),
991                    ..Default::default()
992                };
993                log_trace!("DM_DEV_REMOVE returned dm_ioctl: {:?}", i);
994                current_task.write_object(user_info, &i)?;
995                Ok(SUCCESS)
996            }
997            DM_LIST_DEVICES => {
998                if flags.contains(DeviceMapperFlags::DM_NAME_LIST_HAS_UUID)
999                    || flags.contains(DeviceMapperFlags::DM_NAME_LIST_NO_UUID)
1000                {
1001                    return error!(ENOTSUP);
1002                }
1003                let mut name_list_addr = user_info.next()?.addr();
1004                let mut total_size = std::mem::size_of::<uapi::dm_ioctl>() as u32;
1005                for (_, device) in self.registry.devices.lock().iter() {
1006                    let state = device.state.lock();
1007                    let dm_name_list = UserRef::<uapi::dm_name_list>::new(name_list_addr);
1008                    let name = state.name.iter().map(|v| *v as u8).collect::<Vec<u8>>();
1009                    let name_c_str = std::ffi::CStr::from_bytes_until_nul(name.as_slice())
1010                        .map_err(|_| errno!(EINVAL))?;
1011                    let mut name_vec_with_nul = name_c_str.to_bytes_with_nul().to_vec();
1012                    let mut size = (std::mem::size_of::<uapi::dm_name_list>() - 4
1013                        + name_vec_with_nul.len()) as u32;
1014                    let mut padding = 0;
1015                    if size % 8 != 0 {
1016                        padding = 8 - (size % 8);
1017                        size += padding;
1018                    };
1019                    // For the event_nr and flags.
1020                    size += 8;
1021                    let name_list = uapi::dm_name_list {
1022                        dev: device.number.bits(),
1023                        next: size,
1024                        ..Default::default()
1025                    };
1026                    if total_size + size > info.data_size {
1027                        let i = uapi::dm_ioctl {
1028                            data_size: DATA_SIZE,
1029                            data_start: std::mem::size_of::<uapi::dm_ioctl>() as u32,
1030                            flags: DeviceMapperFlags::BUFFER_FULL.bits(),
1031                            ..Default::default()
1032                        };
1033                        log_trace!("DM_LIST_DEVICES returned dm_ioctl: {:?}", i);
1034                        current_task.write_object(user_info, &i)?;
1035                        return Ok(SUCCESS);
1036                    }
1037                    total_size += size;
1038                    let mut name_addr = dm_name_list.next()?.addr();
1039                    name_addr = name_addr.sub(4 as usize)?;
1040                    current_task.write_object(dm_name_list, &name_list)?;
1041                    name_vec_with_nul.extend(vec![0; padding as usize + 8]);
1042                    current_task.write_memory(name_addr, name_vec_with_nul.as_slice())?;
1043                    name_list_addr =
1044                        name_list_addr.checked_add(size as usize).ok_or_else(|| errno!(EINVAL))?;
1045                }
1046                let i = uapi::dm_ioctl {
1047                    data_size: total_size,
1048                    data_start: std::mem::size_of::<uapi::dm_ioctl>() as u32,
1049                    ..Default::default()
1050                };
1051                log_trace!("DM_LIST_DEVICE returned dm_ioctl: {:?}", i);
1052                current_task.write_object(user_info, &i)?;
1053                Ok(SUCCESS)
1054            }
1055            DM_LIST_VERSIONS => {
1056                let version_list_addr = user_info.next()?.addr();
1057                let dm_versions_list = UserRef::<uapi::dm_target_versions>::new(version_list_addr);
1058                let name_c_str =
1059                    std::ffi::CString::new(String::from("verity")).map_err(|_| errno!(EINVAL))?;
1060                let mut name_vec_with_nul = name_c_str.as_bytes_with_nul().to_vec();
1061                let mut size = (std::mem::size_of::<uapi::dm_target_versions>()
1062                    + name_vec_with_nul.len()) as u32;
1063                let mut padding = 0;
1064                if size % 8 != 0 {
1065                    padding = 8 - (size % 8);
1066                    size += padding;
1067                };
1068                name_vec_with_nul.extend(vec![0; padding as usize]);
1069                if std::mem::size_of::<uapi::dm_ioctl>() as u32 + size > info.data_size {
1070                    let i = uapi::dm_ioctl {
1071                        data_size: DATA_SIZE,
1072                        data_start: 0,
1073                        flags: DeviceMapperFlags::BUFFER_FULL.bits(),
1074                        ..Default::default()
1075                    };
1076                    log_trace!("DM_LIST_VERSIONS returned dm_ioctl: {:?}", i);
1077                    current_task.write_object(user_info, &i)?;
1078                    return Ok(SUCCESS);
1079                }
1080                let target_versions = uapi::dm_target_versions {
1081                    next: size,
1082                    version: [
1083                        DM_VERITY_VERSION_MAJOR,
1084                        DM_VERITY_VERSION_MINOR,
1085                        DM_VERITY_VERSION_PATCHLEVEL,
1086                    ],
1087                    ..Default::default()
1088                };
1089                let name_addr = dm_versions_list.next()?.addr();
1090                current_task.write_object(dm_versions_list, &target_versions)?;
1091                current_task.write_memory(name_addr, name_vec_with_nul.as_slice())?;
1092
1093                let i = uapi::dm_ioctl {
1094                    data_size: std::mem::size_of::<uapi::dm_ioctl>() as u32 + size,
1095                    data_start: std::mem::size_of::<uapi::dm_ioctl>() as u32,
1096                    ..Default::default()
1097                };
1098                log_trace!("DM_LIST_VERSIONS returned dm_ioctl: {:?}", i);
1099                current_task.write_object(user_info, &i)?;
1100                Ok(SUCCESS)
1101            }
1102            DM_TABLE_STATUS => {
1103                let dm_device = self.registry.get(&info)?;
1104                let state = dm_device.state.lock();
1105                let mut total_data_size = 0;
1106                let mut data_padding = 0;
1107                let mut target_spec_addr = user_info.next()?.addr();
1108                let mut out_flags = DeviceMapperFlags::empty();
1109                let space_for_data = info.data_size as usize
1110                    - std::cmp::min(info.data_size as usize, std::mem::size_of::<uapi::dm_ioctl>());
1111                if let Some(active_table) = &state.active_table {
1112                    for target in &active_table.targets {
1113                        let target_spec_info =
1114                            UserRef::<uapi::dm_target_spec>::new(target_spec_addr);
1115                        let mut data_size = std::mem::size_of::<uapi::dm_target_spec>();
1116                        if total_data_size + data_size - data_padding >= space_for_data {
1117                            out_flags |= DeviceMapperFlags::BUFFER_FULL;
1118                            break;
1119                        }
1120                        let data_addr = target_spec_info.next()?.addr();
1121                        if flags.contains(DeviceMapperFlags::STATUS_TABLE) {
1122                            match &target.target_type {
1123                                TargetType::Verity(args) => {
1124                                    let param_str = std::ffi::CString::new(args.parameter_string())
1125                                        .map_err(|_| errno!(EINVAL))?;
1126                                    let mut args_bytes = param_str.into_bytes_with_nul();
1127                                    if args_bytes.len() % 8 != 0 {
1128                                        data_padding = 8 - args_bytes.len() % 8;
1129                                        args_bytes.extend(vec![0 as u8; data_padding]);
1130                                    }
1131                                    data_size += args_bytes.len();
1132                                    if total_data_size + data_size - data_padding >= space_for_data
1133                                    {
1134                                        out_flags |= DeviceMapperFlags::BUFFER_FULL;
1135                                        break;
1136                                    }
1137                                    current_task.write_memory(data_addr, args_bytes.as_slice())?;
1138                                }
1139                                TargetType::Error => {
1140                                    // Error target has no parameters.
1141                                }
1142                            }
1143                        } else if flags.contains(DeviceMapperFlags::IMA_MEASUREMENT) {
1144                            // Linux 6.6.15 does not currently support IMA for dm-verity.
1145                            return error!(ENOTSUP);
1146                        } else {
1147                            match &target.target_type {
1148                                TargetType::Verity(args) => {
1149                                    let status = if args.corrupted { "C" } else { "V" };
1150                                    let status_c_str = std::ffi::CString::new(String::from(status))
1151                                        .map_err(|_| errno!(EINVAL))?;
1152                                    let mut status_bytes = status_c_str.into_bytes_with_nul();
1153                                    if status_bytes.len() % 8 != 0 {
1154                                        data_padding = 8 - status_bytes.len() % 8;
1155                                        status_bytes.extend(vec![0 as u8; data_padding]);
1156                                    }
1157                                    data_size += status_bytes.len();
1158                                    if total_data_size + data_size - data_padding >= space_for_data
1159                                    {
1160                                        out_flags |= DeviceMapperFlags::BUFFER_FULL;
1161                                        break;
1162                                    }
1163                                    current_task
1164                                        .write_memory(data_addr, status_bytes.as_slice())?;
1165                                }
1166                                TargetType::Error => {
1167                                    // Status for error target is not used much but we can provide something.
1168                                }
1169                            }
1170                        }
1171                        total_data_size += data_size;
1172                        let target_spec = uapi::dm_target_spec {
1173                            sector_start: target.sector_start,
1174                            length: target.length,
1175                            status: target.status,
1176                            target_type: target.name,
1177                            next: total_data_size as u32,
1178                        };
1179                        current_task.write_object(target_spec_info, &target_spec)?;
1180                        target_spec_addr = target_spec_addr
1181                            .checked_add(data_size)
1182                            .ok_or_else(|| errno!(EINVAL))?;
1183                    }
1184                } else {
1185                    let i = uapi::dm_ioctl {
1186                        name: state.name,
1187                        version: state.version,
1188                        uuid: state.uuid,
1189                        dev: dm_device.number.bits(),
1190                        data_size: DATA_SIZE,
1191                        data_start: std::mem::size_of::<uapi::dm_ioctl>() as u32,
1192                        flags: state.get_flags().bits(),
1193                        ..Default::default()
1194                    };
1195                    log_trace!("DM_TABLE_STATUS returned dm_ioctl: {:?}", i);
1196                    current_task.write_object(user_info, &i)?;
1197                    return Ok(SUCCESS);
1198                }
1199                // Linux removes the size of the data padding when calculating the data size field
1200                // returned.
1201                let total_size = if out_flags.contains(DeviceMapperFlags::BUFFER_FULL) {
1202                    DATA_SIZE
1203                } else {
1204                    (total_data_size + std::mem::size_of::<uapi::dm_ioctl>() - data_padding) as u32
1205                };
1206                out_flags |= state.get_flags();
1207
1208                let i = uapi::dm_ioctl {
1209                    name: state.name,
1210                    version: state.version,
1211                    uuid: state.uuid,
1212                    dev: dm_device.number.bits(),
1213                    data_size: total_size,
1214                    data_start: std::mem::size_of::<uapi::dm_ioctl>() as u32,
1215                    flags: out_flags.bits(),
1216                    ..Default::default()
1217                };
1218                log_trace!("DM_TABLE_STATUS returned dm_ioctl: {:?}", i);
1219                current_task.write_object(user_info, &i)?;
1220                Ok(SUCCESS)
1221            }
1222            // These dm ioctls are not used by Android
1223            DM_VERSION
1224            | DM_DEV_RENAME
1225            | DM_DEV_WAIT
1226            | DM_TABLE_CLEAR
1227            | DM_TABLE_DEPS
1228            | DM_REMOVE_ALL
1229            | DM_TARGET_MSG
1230            | DM_DEV_SET_GEOMETRY
1231            | DM_DEV_ARM_POLL
1232            | DM_GET_TARGET_VERSION => return error!(ENOTSUP),
1233            _ => error!(ENOTTY),
1234        }
1235    }
1236}
1237
1238pub fn create_device_mapper(
1239    current_task: &CurrentTask,
1240    _id: DeviceId,
1241    _node: &NamespaceNode,
1242    _flags: OpenFlags,
1243) -> Result<Box<dyn FileOps>, Errno> {
1244    Ok(Box::new(DeviceMapper::new(current_task.kernel().expando.get::<DeviceMapperRegistry>())))
1245}
1246
1247fn get_or_create_dm_device(
1248    current_task: &CurrentTask,
1249    id: DeviceId,
1250    _node: &NamespaceNode,
1251    _flags: OpenFlags,
1252) -> Result<Box<dyn FileOps>, Errno> {
1253    Ok(current_task
1254        .kernel()
1255        .expando
1256        .get::<DeviceMapperRegistry>()
1257        .get_or_create_by_minor(current_task, id.minor())?
1258        .create_file_ops())
1259}
1260
1261#[cfg(test)]
1262mod tests {
1263    use super::*;
1264    use starnix_core::testing::spawn_kernel_and_run;
1265
1266    #[::fuchsia::test]
1267    async fn device_mapper_init_registers_sysfs_module_parameters() {
1268        spawn_kernel_and_run(async |current_task| {
1269            let kernel = current_task.kernel();
1270            device_mapper_init(kernel).expect("device_mapper_init should succeed");
1271
1272            let root = &kernel.device_registry.objects.root;
1273            let max_age_seconds = root
1274                .lookup("module/dm_bufio/parameters/max_age_seconds".into())
1275                .expect("dm_bufio max_age_seconds should exist");
1276            assert_eq!(max_age_seconds.info().mode, mode!(IFREG, 0o644));
1277
1278            let prefetch_cluster = root
1279                .lookup("module/dm_verity/parameters/prefetch_cluster".into())
1280                .expect("dm_verity prefetch_cluster should exist");
1281            assert_eq!(prefetch_cluster.info().mode, mode!(IFREG, 0o644));
1282        })
1283        .await;
1284    }
1285}