Skip to main content

starnix_modules_loop/
lib.rs

1// Copyright 2023 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 = "256"]
6
7use bitflags::bitflags;
8use starnix_core::device::DeviceMode;
9use starnix_core::device::block::canonicalize_ioctl_request;
10use starnix_core::device::kobject::{Device, DeviceMetadata};
11use starnix_core::fs::sysfs::{BlockDeviceInfo, build_block_device_directory};
12use starnix_core::mm::memory::MemoryObject;
13use starnix_core::mm::{MemoryAccessorExt, PAGE_SIZE, ProtectionFlags};
14use starnix_core::task::dynamic_thread_spawner::SpawnRequestBuilder;
15use starnix_core::task::{CurrentTask, Kernel};
16use starnix_core::vfs::buffers::{InputBuffer, OutputBuffer};
17use starnix_core::vfs::pseudo::simple_file::{BytesFile, BytesFileOps};
18use starnix_core::vfs::{
19    Buffer, FdNumber, FileHandle, FileObject, FileOps, FsNodeOps, FsString, InputBufferCallback,
20    NamespaceNode, PeekBufferSegmentsCallback, fileops_impl_dataless, fileops_impl_noop_sync,
21    fileops_impl_seekable, fileops_impl_seekless,
22};
23use starnix_ext::map_ext::EntryExt;
24use starnix_logging::track_stub;
25use starnix_sync::{
26    FileOpsCore, LockDepMutex, LockEqualOrBefore, Locked, LoopDeviceStateLock, LoopDevicesLock,
27    Unlocked,
28};
29use starnix_syscalls::{SUCCESS, SyscallArg, SyscallResult};
30use starnix_types::user_buffer::UserBuffer;
31use starnix_uapi::device_id::{DeviceId, LOOP_MAJOR};
32use starnix_uapi::errors::Errno;
33use starnix_uapi::open_flags::OpenFlags;
34use starnix_uapi::user_address::{MultiArchUserRef, UserRef};
35use starnix_uapi::{
36    __kernel_old_dev_t, BLKFLSBUF, BLKGETSIZE, BLKGETSIZE64, BLKRASET, LO_FLAGS_AUTOCLEAR,
37    LO_FLAGS_DIRECT_IO, LO_FLAGS_PARTSCAN, LO_FLAGS_READ_ONLY, LO_KEY_SIZE, LOOP_CHANGE_FD,
38    LOOP_CLR_FD, LOOP_CONFIGURE, LOOP_CTL_ADD, LOOP_CTL_GET_FREE, LOOP_CTL_REMOVE, LOOP_GET_STATUS,
39    LOOP_GET_STATUS64, LOOP_SET_BLOCK_SIZE, LOOP_SET_CAPACITY, LOOP_SET_DIRECT_IO, LOOP_SET_FD,
40    LOOP_SET_STATUS, LOOP_SET_STATUS64, errno, error, loop_info, loop_info64, mode, uapi,
41};
42use std::borrow::Cow;
43use std::collections::btree_map::{BTreeMap, Entry};
44use std::sync::{Arc, Weak};
45use zx::VmoChildOptions;
46
47// See LOOP_SET_BLOCK_SIZE in <https://man7.org/linux/man-pages/man4/loop.4.html>.
48const MIN_BLOCK_SIZE: u32 = 512;
49
50bitflags! {
51    #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
52    struct LoopDeviceFlags: u32 {
53        const READ_ONLY = LO_FLAGS_READ_ONLY;
54        const AUTOCLEAR = LO_FLAGS_AUTOCLEAR;
55        const PARTSCAN = LO_FLAGS_PARTSCAN;
56        const DIRECT_IO = LO_FLAGS_DIRECT_IO;
57    }
58}
59
60#[derive(Debug)]
61struct LoopDeviceState {
62    backing_file: Option<FileHandle>,
63    block_size: u32,
64    k_device: Option<Device>,
65
66    // See struct loop_info64 for details about these fields.
67    offset: u64,
68    size_limit: u64,
69    flags: LoopDeviceFlags,
70
71    // Encryption is not implemented.
72    encrypt_type: u32,
73    encrypt_key: Vec<u8>,
74    init: [u64; 2],
75}
76
77impl Default for LoopDeviceState {
78    fn default() -> Self {
79        LoopDeviceState {
80            backing_file: Default::default(),
81            block_size: MIN_BLOCK_SIZE,
82            k_device: Default::default(),
83            offset: Default::default(),
84            size_limit: Default::default(),
85            flags: Default::default(),
86            encrypt_type: Default::default(),
87            encrypt_key: Default::default(),
88            init: Default::default(),
89        }
90    }
91}
92
93impl LoopDeviceState {
94    fn check_bound(&self) -> Result<(), Errno> {
95        if self.backing_file.is_none() { error!(ENXIO) } else { Ok(()) }
96    }
97
98    fn set_backing_file(
99        &mut self,
100        locked: &mut Locked<Unlocked>,
101        current_task: &CurrentTask,
102        backing_file: FileHandle,
103    ) -> Result<(), Errno> {
104        if self.backing_file.is_some() {
105            return error!(EBUSY);
106        }
107        self.backing_file = Some(backing_file);
108        self.update_size_limit(locked, current_task)?;
109        Ok(())
110    }
111
112    fn set_info(&mut self, info: &uapi::loop_info64) {
113        let encrypt_key_size = info.lo_encrypt_key_size.clamp(0, LO_KEY_SIZE);
114        self.offset = info.lo_offset;
115        self.size_limit = info.lo_sizelimit;
116        self.flags = LoopDeviceFlags::from_bits_truncate(info.lo_flags);
117        self.encrypt_type = info.lo_encrypt_type;
118        self.encrypt_key = info.lo_encrypt_key[0..(encrypt_key_size as usize)].to_owned();
119        self.init = info.lo_init;
120    }
121
122    fn update_size_limit(
123        &mut self,
124        locked: &mut Locked<Unlocked>,
125        current_task: &CurrentTask,
126    ) -> Result<(), Errno> {
127        if let Some(backing_file) = &self.backing_file {
128            let backing_stat = backing_file.node().stat(locked, current_task)?;
129            self.size_limit = backing_stat.st_size as u64;
130        }
131        Ok(())
132    }
133
134    fn set_k_device(&mut self, k_device: Device) {
135        self.k_device = Some(k_device);
136    }
137}
138
139#[derive(Debug, Default)]
140struct LoopDevice {
141    number: u32,
142    state: LockDepMutex<LoopDeviceState, LoopDeviceStateLock>,
143}
144
145struct LoopDeviceBackingFile(Weak<LoopDevice>);
146
147impl LoopDeviceBackingFile {
148    pub fn new_node(device: Weak<LoopDevice>) -> impl FsNodeOps {
149        BytesFile::new_node(Self(device))
150    }
151}
152
153impl BytesFileOps for LoopDeviceBackingFile {
154    fn read(&self, current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
155        let mut path = self
156            .0
157            .upgrade()
158            .ok_or_else(|| errno!(EINVAL))?
159            .state
160            .lock()
161            .backing_file
162            .as_ref()
163            .ok_or_else(|| errno!(EINVAL))
164            .map(|file| file.name.to_passive().path(&current_task.fs()))?;
165        path.push(b'\n');
166        Ok(Cow::Owned(path.into()))
167    }
168}
169
170impl LoopDevice {
171    fn new<'a, L>(locked: &mut Locked<L>, kernel: &Kernel, minor: u32) -> Result<Arc<Self>, Errno>
172    where
173        L: LockEqualOrBefore<FileOpsCore>,
174    {
175        let registry = &kernel.device_registry;
176        let loop_device_name = FsString::from(format!("loop{minor}"));
177        let virtual_block_class = registry.objects.virtual_block_class();
178        let device = Arc::new(Self { number: minor, state: Default::default() });
179        let device_weak = Arc::<LoopDevice>::downgrade(&device);
180        let k_device = registry.add_device(
181            locked,
182            kernel,
183            loop_device_name.as_ref(),
184            DeviceMetadata::new(
185                loop_device_name.clone(),
186                DeviceId::new(LOOP_MAJOR, minor),
187                DeviceMode::Block,
188            ),
189            virtual_block_class,
190            |device, dir| {
191                let device_weak_clone = device_weak.clone();
192                build_block_device_directory(device, device_weak, dir);
193                dir.subdir("loop", 0o755, |dir| {
194                    dir.entry(
195                        "backing_file",
196                        LoopDeviceBackingFile::new_node(device_weak_clone),
197                        mode!(IFREG, 0o644),
198                    );
199                });
200            },
201        )?;
202        {
203            let mut state = device.state.lock();
204            state.set_k_device(k_device);
205        }
206        Ok(device)
207    }
208
209    fn create_file_ops(self: &Arc<Self>) -> Box<dyn FileOps> {
210        Box::new(LoopDeviceFile { device: self.clone() })
211    }
212
213    fn backing_file(&self) -> Option<FileHandle> {
214        self.state.lock().backing_file.clone()
215    }
216
217    fn is_bound(&self) -> bool {
218        self.state.lock().backing_file.is_some()
219    }
220
221    fn offset_for_backing_file(&self, offset: usize) -> usize {
222        self.state.lock().offset.saturating_add(offset as u64) as usize
223    }
224}
225
226fn check_block_size(block_size: u32) -> Result<(), Errno> {
227    let page_size = *PAGE_SIZE as u32;
228    let mut allowed_size = MIN_BLOCK_SIZE;
229    while allowed_size <= page_size {
230        if block_size == allowed_size {
231            return Ok(());
232        }
233        allowed_size *= 2;
234    }
235    error!(EINVAL)
236}
237
238impl BlockDeviceInfo for LoopDevice {
239    fn size(&self) -> Result<usize, Errno> {
240        Ok(self.state.lock().size_limit as usize)
241    }
242}
243
244#[derive(Debug)]
245struct CroppedInputBuffer<'a> {
246    base: &'a mut dyn InputBuffer,
247    size: usize,
248    drained: bool,
249}
250
251impl<'a> CroppedInputBuffer<'a> {
252    fn new(base: &'a mut dyn InputBuffer, size: usize) -> Self {
253        debug_assert!(size <= base.bytes_read() + base.available());
254        CroppedInputBuffer { base, size, drained: false }
255    }
256}
257
258impl<'a> Buffer for CroppedInputBuffer<'a> {
259    fn segments_count(&self) -> Result<usize, Errno> {
260        error!(ENOTSUP)
261    }
262
263    fn peek_each_segment(
264        &mut self,
265        callback: &mut PeekBufferSegmentsCallback<'_>,
266    ) -> Result<(), Errno> {
267        let mut pos = 0;
268        self.base.peek_each_segment(&mut |buffer: &UserBuffer| {
269            if pos >= self.size {
270                return;
271            } else if pos + buffer.length > self.size {
272                let cropped_size = self.size - pos;
273                pos += buffer.length;
274                callback(&UserBuffer { address: buffer.address, length: cropped_size });
275            } else {
276                pos += buffer.length;
277                callback(buffer);
278            }
279        })
280    }
281}
282
283impl<'a> InputBuffer for CroppedInputBuffer<'a> {
284    fn peek_each(&mut self, callback: &mut InputBufferCallback<'_>) -> Result<usize, Errno> {
285        if self.drained {
286            return Ok(0);
287        }
288        let mut pos = self.base.bytes_read();
289        self.base.peek_each(&mut |buf: &[u8]| {
290            if pos >= self.size {
291                return Ok(0);
292            }
293            let size = std::cmp::min(buf.len(), self.size - pos);
294            pos += size;
295            callback(&buf[..size])
296        })
297    }
298    fn advance(&mut self, length: usize) -> Result<(), Errno> {
299        if length > self.available() {
300            return error!(EINVAL);
301        }
302        self.base.advance(length)
303    }
304    fn available(&self) -> usize {
305        if self.drained || self.size < self.bytes_read() {
306            0
307        } else {
308            self.size - self.bytes_read()
309        }
310    }
311    fn bytes_read(&self) -> usize {
312        self.base.bytes_read()
313    }
314    fn drain(&mut self) -> usize {
315        let size = self.available();
316        self.drained = true;
317        size
318    }
319}
320
321struct LoopDeviceFile {
322    device: Arc<LoopDevice>,
323}
324
325impl FileOps for LoopDeviceFile {
326    fileops_impl_seekable!();
327
328    fn read(
329        &self,
330        locked: &mut Locked<FileOpsCore>,
331        _file: &FileObject,
332        current_task: &CurrentTask,
333        offset: usize,
334        data: &mut dyn OutputBuffer,
335    ) -> Result<usize, Errno> {
336        if let Some(backing_file) = self.device.backing_file() {
337            backing_file.read_at(
338                locked,
339                current_task,
340                self.device.offset_for_backing_file(offset),
341                data,
342            )
343        } else {
344            Ok(0)
345        }
346    }
347
348    fn write(
349        &self,
350        locked: &mut Locked<FileOpsCore>,
351        _file: &FileObject,
352        current_task: &CurrentTask,
353        offset: usize,
354        data: &mut dyn InputBuffer,
355    ) -> Result<usize, Errno> {
356        if let Some(backing_file) = self.device.backing_file() {
357            let limit = self.device.state.lock().size_limit as usize;
358            if offset >= limit {
359                // Can't write past the size limit.
360                return Ok(0);
361            }
362            let mut cropped_buf;
363            let data = if offset + data.available() > limit {
364                // If the write would exceed the size limit, then crop the input buffer to write
365                // to the limit without exceeding it.
366                let bytes_to_write = limit - offset;
367                let cropped_size = data.bytes_read() + bytes_to_write;
368                cropped_buf = CroppedInputBuffer::new(data, cropped_size);
369                &mut cropped_buf
370            } else {
371                data
372            };
373            let r = backing_file.write_at(
374                locked,
375                current_task,
376                self.device.offset_for_backing_file(offset),
377                data,
378            );
379            r
380        } else {
381            error!(ENOSPC)
382        }
383    }
384
385    fn get_memory(
386        &self,
387        locked: &mut Locked<FileOpsCore>,
388        _file: &FileObject,
389        current_task: &CurrentTask,
390        requested_length: Option<usize>,
391        prot: ProtectionFlags,
392    ) -> Result<Arc<MemoryObject>, Errno> {
393        let backing_file = self.device.backing_file().ok_or_else(|| errno!(EBADF))?;
394
395        let state = self.device.state.lock();
396        let configured_offset = state.offset;
397        let configured_size_limit = match state.size_limit {
398            // If the size limit is 0, use all available bytes from the backing file.
399            0 => None,
400            n => Some(n),
401        };
402
403        let backing_memory = backing_file.get_memory(
404            locked,
405            current_task,
406            requested_length.map(|l| l + configured_offset as usize),
407            prot,
408        )?;
409        let backing_memory_size = backing_memory.get_size();
410
411        let clone_len = backing_memory_size
412            .min(configured_size_limit.unwrap_or(u64::MAX))
413            .min(requested_length.unwrap_or(usize::MAX) as u64);
414
415        let backing_content_size = backing_memory.get_content_size();
416
417        let mem = if backing_content_size < clone_len {
418            // If we need to set a content size then the backing memory must not be writable since
419            // we are going to create a snapshot clone, which will prevent any writes from becoming
420            // visible in the backing memory.
421            if backing_file.can_write() {
422                track_stub!(
423                    TODO("https://fxbug.dev/408048145"),
424                    "Loop device mutable loop backing files with smaller content"
425                );
426                return error!(EINVAL);
427            }
428            let memory_clone = backing_memory
429                .create_child(
430                    VmoChildOptions::SNAPSHOT_AT_LEAST_ON_WRITE,
431                    configured_offset,
432                    clone_len,
433                )
434                .map_err(|e| errno!(EINVAL, e))?;
435            let new_content_size = backing_content_size.saturating_sub(configured_offset);
436            memory_clone.set_content_size(new_content_size).map_err(|e| errno!(EINVAL, e))?;
437            memory_clone
438        } else {
439            backing_memory
440                .create_child(VmoChildOptions::SLICE, configured_offset, clone_len)
441                .map_err(|e| errno!(EINVAL, e))?
442        };
443        Ok(Arc::new(mem))
444    }
445
446    fn sync(&self, _file: &FileObject, current_task: &CurrentTask) -> Result<(), Errno> {
447        if let Some(f) = self.device.backing_file() { f.sync(current_task) } else { Ok(()) }
448    }
449
450    fn ioctl(
451        &self,
452        locked: &mut Locked<Unlocked>,
453        file: &FileObject,
454        current_task: &CurrentTask,
455        request: u32,
456        arg: SyscallArg,
457    ) -> Result<SyscallResult, Errno> {
458        match canonicalize_ioctl_request(current_task, request) {
459            BLKGETSIZE => {
460                let user_size = MultiArchUserRef::<u64, u32>::new(current_task, arg);
461                let state = self.device.state.lock();
462                state.check_bound()?;
463                let size = state.size_limit / (state.block_size as u64);
464                std::mem::drop(state);
465                current_task.write_multi_arch_object(user_size, size)?;
466                Ok(SUCCESS)
467            }
468            BLKGETSIZE64 => {
469                let user_size = UserRef::<u64>::from(arg);
470                let state = self.device.state.lock();
471                state.check_bound()?;
472                let size = state.size_limit;
473                std::mem::drop(state);
474                current_task.write_object(user_size, &size)?;
475                Ok(SUCCESS)
476            }
477            BLKFLSBUF => {
478                track_stub!(TODO("https://fxbug.dev/322873756"), "Loop device BLKFLSBUF");
479                Ok(SUCCESS)
480            }
481            BLKRASET => {
482                track_stub!(TODO("https://fxbug.dev/408500542"), "Loop device BLKRASET");
483                Ok(SUCCESS)
484            }
485            LOOP_SET_FD => {
486                let fd = arg.into();
487                let backing_file = current_task.files().get(fd)?;
488                let mut state = self.device.state.lock();
489                state.set_backing_file(locked, current_task, backing_file)?;
490                Ok(SUCCESS)
491            }
492            LOOP_CLR_FD => {
493                let mut state = self.device.state.lock();
494                state.check_bound()?;
495                *state = Default::default();
496                Ok(SUCCESS)
497            }
498            LOOP_SET_STATUS => {
499                let modifiable_flags = LoopDeviceFlags::AUTOCLEAR | LoopDeviceFlags::PARTSCAN;
500
501                let user_info = UserRef::<uapi::loop_info>::from(arg);
502                let info = current_task.read_object(user_info)?;
503                let flags = LoopDeviceFlags::from_bits_truncate(info.lo_flags as u32);
504                let encrypt_key_size = info.lo_encrypt_key_size.clamp(0, LO_KEY_SIZE as i32);
505                let mut state = self.device.state.lock();
506                state.check_bound()?;
507                state.flags = (state.flags & !modifiable_flags) | (flags & modifiable_flags);
508                state.encrypt_type = info.lo_encrypt_type as u32;
509                state.encrypt_key = info.lo_encrypt_key[0..(encrypt_key_size as usize)].to_owned();
510                state.init = info.lo_init;
511                state.offset = info.lo_offset as u64;
512                std::mem::drop(state);
513                Ok(SUCCESS)
514            }
515            LOOP_GET_STATUS => {
516                let user_info = UserRef::<uapi::loop_info>::from(arg);
517                let node = file.node();
518                let rdev = node.info().rdev;
519                let state = self.device.state.lock();
520                state.check_bound()?;
521                let info = loop_info {
522                    lo_number: self.device.number as i32,
523                    lo_device: node.dev().bits() as __kernel_old_dev_t,
524                    lo_inode: node.ino,
525                    lo_rdevice: rdev.bits() as __kernel_old_dev_t,
526                    lo_offset: state.offset as i32,
527                    lo_encrypt_type: state.encrypt_type as i32,
528                    lo_flags: state.flags.bits() as i32,
529                    lo_init: state.init,
530                    ..Default::default()
531                };
532                std::mem::drop(state);
533                current_task.write_object(user_info, &info)?;
534                Ok(SUCCESS)
535            }
536            LOOP_CHANGE_FD => {
537                let fd = arg.into();
538                let backing_file = current_task.files().get(fd)?;
539                let mut state = self.device.state.lock();
540                if let Some(_existing_file) = &state.backing_file {
541                    // https://man7.org/linux/man-pages/man4/loop.4.html says:
542                    //
543                    //   This operation is possible only if the loop device is read-only and the
544                    //   new backing store is the same size and type as the old backing store.
545                    if !state.flags.contains(LoopDeviceFlags::READ_ONLY) {
546                        return error!(EINVAL);
547                    }
548                    track_stub!(
549                        TODO("https://fxbug.dev/322874313"),
550                        "check backing store size before change loop fd"
551                    );
552                    state.backing_file = Some(backing_file);
553                    Ok(SUCCESS)
554                } else {
555                    error!(EINVAL)
556                }
557            }
558            LOOP_SET_CAPACITY => {
559                let mut state = self.device.state.lock();
560                state.check_bound()?;
561                state.update_size_limit(locked, current_task)?;
562                Ok(SUCCESS)
563            }
564            LOOP_SET_DIRECT_IO => {
565                track_stub!(TODO("https://fxbug.dev/322873418"), "Loop device LOOP_SET_DIRECT_IO");
566                error!(ENOTTY)
567            }
568            LOOP_SET_BLOCK_SIZE => {
569                let block_size = arg.into();
570                check_block_size(block_size)?;
571                let mut state = self.device.state.lock();
572                state.check_bound()?;
573                state.block_size = block_size;
574                Ok(SUCCESS)
575            }
576            LOOP_CONFIGURE => {
577                let user_config = UserRef::<uapi::loop_config>::from(arg);
578                let config = current_task.read_object(user_config)?;
579                let fd = FdNumber::from_raw(config.fd as i32);
580                check_block_size(config.block_size)?;
581                let mut state = self.device.state.lock();
582                if let Ok(backing_file) = current_task.files().get(fd) {
583                    state.set_backing_file(locked, current_task, backing_file)?;
584                }
585                state.block_size = config.block_size;
586                state.set_info(&config.info);
587                std::mem::drop(state);
588                Ok(SUCCESS)
589            }
590            LOOP_SET_STATUS64 => {
591                let user_info = UserRef::<uapi::loop_info64>::from(arg);
592                let info = current_task.read_object(user_info)?;
593                let mut state = self.device.state.lock();
594                state.check_bound()?;
595                state.set_info(&info);
596                std::mem::drop(state);
597                Ok(SUCCESS)
598            }
599            LOOP_GET_STATUS64 => {
600                let user_info = UserRef::<uapi::loop_info64>::from(arg);
601                let node = file.node();
602                let rdev = node.info().rdev;
603                let state = self.device.state.lock();
604                state.check_bound()?;
605                let info = loop_info64 {
606                    lo_device: node.dev().bits(),
607                    lo_inode: node.ino,
608                    lo_rdevice: rdev.bits(),
609                    lo_offset: state.offset as u64,
610                    lo_sizelimit: state.size_limit,
611                    lo_number: self.device.number,
612                    lo_encrypt_type: state.encrypt_type,
613                    lo_flags: state.flags.bits(),
614                    lo_init: state.init,
615                    ..Default::default()
616                };
617                std::mem::drop(state);
618                current_task.write_object(user_info, &info)?;
619                Ok(SUCCESS)
620            }
621            _ => error!(ENOTTY),
622        }
623    }
624}
625
626pub fn loop_device_init(locked: &mut Locked<Unlocked>, kernel: &Kernel) -> Result<(), Errno> {
627    // Device registry.
628    kernel
629        .device_registry
630        .register_major(
631            locked,
632            "loop".into(),
633            DeviceMode::Block,
634            LOOP_MAJOR,
635            get_or_create_loop_device,
636        )
637        .expect("loop device register failed.");
638
639    // Ensure initial loop devices.
640    kernel.expando.get::<LoopDeviceRegistry>().ensure_initial_devices(locked, kernel)
641}
642
643#[derive(Debug, Default)]
644pub struct LoopDeviceRegistry {
645    devices: LockDepMutex<BTreeMap<u32, Arc<LoopDevice>>, LoopDevicesLock>,
646}
647
648impl LoopDeviceRegistry {
649    /// Ensure initial loop devices.
650    fn ensure_initial_devices<L>(
651        &self,
652        locked: &mut Locked<L>,
653        kernel: &Kernel,
654    ) -> Result<(), Errno>
655    where
656        L: LockEqualOrBefore<FileOpsCore>,
657    {
658        for minor in 0..8 {
659            self.get_or_create(locked, kernel, minor)?;
660        }
661        Ok(())
662    }
663
664    fn get(&self, minor: u32) -> Result<Arc<LoopDevice>, Errno> {
665        self.devices.lock().get(&minor).ok_or_else(|| errno!(ENODEV)).cloned()
666    }
667
668    fn get_or_create<'a, L>(
669        &self,
670        locked: &mut Locked<L>,
671        kernel: &Kernel,
672        minor: u32,
673    ) -> Result<Arc<LoopDevice>, Errno>
674    where
675        L: LockEqualOrBefore<FileOpsCore>,
676    {
677        self.devices
678            .lock()
679            .entry(minor)
680            .or_insert_with_fallible(|| LoopDevice::new(locked, kernel, minor))
681            .cloned()
682    }
683
684    fn find<L>(&self, locked: &mut Locked<L>, current_task: &CurrentTask) -> Result<u32, Errno>
685    where
686        L: LockEqualOrBefore<FileOpsCore>,
687    {
688        let mut devices = self.devices.lock();
689        for minor in 0..u32::MAX {
690            match devices.entry(minor) {
691                Entry::Vacant(e) => {
692                    e.insert(LoopDevice::new(locked, current_task.kernel(), minor)?);
693                    return Ok(minor);
694                }
695                Entry::Occupied(e) => {
696                    if !e.get().is_bound() {
697                        return Ok(minor);
698                    }
699                }
700            }
701        }
702        error!(ENODEV)
703    }
704
705    fn add<L>(
706        &self,
707        locked: &mut Locked<L>,
708        current_task: &CurrentTask,
709        minor: u32,
710    ) -> Result<(), Errno>
711    where
712        L: LockEqualOrBefore<FileOpsCore>,
713    {
714        match self.devices.lock().entry(minor) {
715            Entry::Vacant(e) => {
716                e.insert(LoopDevice::new(locked, current_task.kernel(), minor)?);
717                Ok(())
718            }
719            Entry::Occupied(_) => {
720                error!(EEXIST)
721            }
722        }
723    }
724
725    fn remove<L>(
726        &self,
727        locked: &mut Locked<L>,
728        current_task: &CurrentTask,
729        k_device: Option<Device>,
730        minor: u32,
731    ) -> Result<(), Errno>
732    where
733        L: LockEqualOrBefore<FileOpsCore>,
734    {
735        match self.devices.lock().entry(minor) {
736            Entry::Vacant(_) => Ok(()),
737            Entry::Occupied(e) => {
738                if e.get().is_bound() {
739                    return error!(EBUSY);
740                }
741                e.remove();
742                let kernel = current_task.kernel();
743                let registry = &kernel.device_registry;
744                if let Some(dev) = &k_device {
745                    registry.remove_device(locked, current_task, dev.clone());
746                } else {
747                    return error!(EINVAL);
748                }
749                Ok(())
750            }
751        }
752    }
753}
754
755pub fn create_loop_control_device(
756    _locked: &mut Locked<FileOpsCore>,
757    current_task: &CurrentTask,
758    _id: DeviceId,
759    _node: &NamespaceNode,
760    _flags: OpenFlags,
761) -> Result<Box<dyn FileOps>, Errno> {
762    Ok(Box::new(LoopControlDevice::new(current_task.kernel().expando.get::<LoopDeviceRegistry>())))
763}
764
765struct LoopControlDevice {
766    registry: Arc<LoopDeviceRegistry>,
767}
768
769impl LoopControlDevice {
770    pub fn new(registry: Arc<LoopDeviceRegistry>) -> Self {
771        Self { registry }
772    }
773}
774
775impl FileOps for LoopControlDevice {
776    fileops_impl_seekless!();
777    fileops_impl_dataless!();
778    fileops_impl_noop_sync!();
779
780    fn ioctl(
781        &self,
782        locked: &mut Locked<Unlocked>,
783        _file: &FileObject,
784        current_task: &CurrentTask,
785        request: u32,
786        arg: SyscallArg,
787    ) -> Result<SyscallResult, Errno> {
788        match request {
789            LOOP_CTL_GET_FREE => Ok(self.registry.find(locked, current_task)?.into()),
790            LOOP_CTL_ADD => {
791                let minor = arg.into();
792                let registry = Arc::clone(&self.registry);
793                // Delegate to the system task to have the permission to create the loop device.
794                let closure = move |locked: &mut Locked<Unlocked>, task: &CurrentTask| {
795                    registry.add(locked, task, minor)
796                };
797                let (result, req) = SpawnRequestBuilder::new()
798                    .with_debug_name("loop-control-add")
799                    .with_sync_closure(closure)
800                    .build_with_sync_result();
801                current_task.kernel().kthreads.spawner().spawn_from_request(req);
802
803                result()??;
804
805                Ok(minor.into())
806            }
807            LOOP_CTL_REMOVE => {
808                let minor = arg.into();
809                let device = self.registry.get(minor)?;
810                let k_device = {
811                    let state = device.state.lock();
812                    state.k_device.clone()
813                };
814                self.registry.remove(locked, current_task, k_device, minor)?;
815                Ok(minor.into())
816            }
817            _ => error!(ENOTTY),
818        }
819    }
820}
821
822fn get_or_create_loop_device(
823    locked: &mut Locked<FileOpsCore>,
824    current_task: &CurrentTask,
825    id: DeviceId,
826    _node: &NamespaceNode,
827    _flags: OpenFlags,
828) -> Result<Box<dyn FileOps>, Errno> {
829    Ok(current_task
830        .kernel()
831        .expando
832        .get::<LoopDeviceRegistry>()
833        .get_or_create(locked, current_task.kernel(), id.minor())?
834        .create_file_ops())
835}
836
837#[cfg(test)]
838mod tests {
839    use super::*;
840    use fidl::endpoints::Proxy;
841    use fidl_fuchsia_io as fio;
842    use starnix_core::fs::fuchsia::new_remote_file;
843    use starnix_core::testing::*;
844    use starnix_core::vfs::buffers::*;
845    use starnix_core::vfs::pseudo::dynamic_file::{DynamicFile, DynamicFileBuf, DynamicFileSource};
846    use starnix_core::vfs::{FdFlags, FsNodeOps};
847
848    #[derive(Clone)]
849    struct PassthroughTestFile(Vec<u8>);
850
851    impl PassthroughTestFile {
852        pub fn new_node(bytes: &[u8]) -> impl FsNodeOps {
853            DynamicFile::new_node(Self(bytes.to_owned()))
854        }
855    }
856
857    impl DynamicFileSource for PassthroughTestFile {
858        fn generate(
859            &self,
860            _current_task: &CurrentTask,
861            sink: &mut DynamicFileBuf,
862        ) -> Result<(), Errno> {
863            sink.write(&self.0);
864            Ok(())
865        }
866    }
867
868    fn bind_simple_loop_device(
869        locked: &mut Locked<Unlocked>,
870        current_task: &CurrentTask,
871        backing_file: FileHandle,
872        open_flags: OpenFlags,
873    ) -> FileHandle {
874        let backing_fd = current_task.add_file(locked, backing_file, FdFlags::empty()).unwrap();
875
876        let loop_file = anon_test_file(
877            locked,
878            &current_task,
879            Box::new(LoopDeviceFile { device: Arc::new(LoopDevice::default()) }),
880            open_flags,
881        );
882
883        let config_addr = map_object_anywhere(
884            locked,
885            &current_task,
886            &uapi::loop_config {
887                block_size: MIN_BLOCK_SIZE,
888                fd: backing_fd.raw() as u32,
889                ..Default::default()
890            },
891        );
892        loop_file.ioctl(locked, &current_task, LOOP_CONFIGURE, config_addr.into()).unwrap();
893
894        loop_file
895    }
896
897    #[::fuchsia::test]
898    async fn basic_read() {
899        spawn_kernel_and_run(async |locked, current_task| {
900            let fs = create_testfs(locked, &current_task.kernel());
901            let expected_contents = b"hello, world!";
902
903            let ops = PassthroughTestFile::new_node(expected_contents);
904            let backing_node = create_fs_node_for_testing(&fs, ops);
905            let file_ops =
906                backing_node.create_file_ops(locked, current_task, OpenFlags::RDONLY).unwrap();
907            let backing_file = anon_test_file(locked, current_task, file_ops, OpenFlags::RDONLY);
908            let loop_file =
909                bind_simple_loop_device(locked, current_task, backing_file, OpenFlags::RDONLY);
910
911            let mut buf = VecOutputBuffer::new(expected_contents.len());
912            loop_file.read(locked, current_task, &mut buf).unwrap();
913
914            assert_eq!(buf.data(), expected_contents);
915        })
916        .await;
917    }
918
919    #[::fuchsia::test]
920    async fn offset_works() {
921        spawn_kernel_and_run(async |locked, current_task| {
922            let fs = create_testfs(locked, &current_task.kernel());
923            let ops = PassthroughTestFile::new_node(b"hello, world!");
924            let backing_node = create_fs_node_for_testing(&fs, ops);
925            let file_ops =
926                backing_node.create_file_ops(locked, current_task, OpenFlags::RDONLY).unwrap();
927            let backing_file = anon_test_file(locked, current_task, file_ops, OpenFlags::RDONLY);
928            let loop_file =
929                bind_simple_loop_device(locked, current_task, backing_file, OpenFlags::RDONLY);
930
931            let info_addr = map_object_anywhere(
932                locked,
933                current_task,
934                &uapi::loop_info64 { lo_offset: 3, ..Default::default() },
935            );
936            loop_file.ioctl(locked, current_task, LOOP_SET_STATUS64, info_addr.into()).unwrap();
937
938            let mut buf = VecOutputBuffer::new(25);
939            loop_file.read(locked, current_task, &mut buf).unwrap();
940
941            assert_eq!(buf.data(), b"lo, world!");
942        })
943        .await;
944    }
945
946    #[::fuchsia::test]
947    async fn basic_get_memory() {
948        let test_data_path = "/pkg/data/testfile.txt";
949        let expected_contents = std::fs::read(test_data_path).unwrap();
950
951        let txt_channel: zx::Channel =
952            fuchsia_fs::file::open_in_namespace(test_data_path, fio::PERM_READABLE)
953                .unwrap()
954                .into_channel()
955                .unwrap()
956                .into();
957
958        spawn_kernel_and_run(async move |locked, current_task| {
959            let backing_file =
960                new_remote_file(locked, current_task, txt_channel.into(), OpenFlags::RDONLY)
961                    .unwrap();
962            let loop_file =
963                bind_simple_loop_device(locked, current_task, backing_file, OpenFlags::RDONLY);
964
965            let memory =
966                loop_file.get_memory(locked, current_task, None, ProtectionFlags::READ).unwrap();
967            let size = memory.get_content_size();
968            let memory_contents = memory.read_to_vec(0, size).unwrap();
969            assert_eq!(memory_contents, expected_contents);
970        })
971        .await;
972    }
973
974    #[::fuchsia::test]
975    async fn get_memory_offset_and_size_limit_work() {
976        // VMO slice children require a page-aligned offset, so we need a file that's big enough to
977        // have multiple pages to support creating a child with a meaningful offset, our own
978        // binary should do the trick.
979        let test_data_path = std::env::args().next().unwrap();
980        let expected_offset = *PAGE_SIZE;
981        let expected_size_limit = *PAGE_SIZE;
982        let expected_contents = std::fs::read(&test_data_path).unwrap();
983        let expected_contents = expected_contents
984            [expected_offset as usize..(expected_offset + expected_size_limit) as usize]
985            .to_vec();
986
987        let txt_channel: zx::Channel =
988            fuchsia_fs::file::open_in_namespace(&test_data_path, fio::PERM_READABLE)
989                .unwrap()
990                .into_channel()
991                .unwrap()
992                .into();
993
994        spawn_kernel_and_run(async move |locked, current_task| {
995            let backing_file =
996                new_remote_file(locked, current_task, txt_channel.into(), OpenFlags::RDONLY)
997                    .unwrap();
998            let loop_file =
999                bind_simple_loop_device(locked, current_task, backing_file, OpenFlags::RDONLY);
1000
1001            let info_addr = map_object_anywhere(
1002                locked,
1003                current_task,
1004                &uapi::loop_info64 {
1005                    lo_offset: expected_offset,
1006                    lo_sizelimit: expected_size_limit,
1007                    ..Default::default()
1008                },
1009            );
1010            loop_file.ioctl(locked, current_task, LOOP_SET_STATUS64, info_addr.into()).unwrap();
1011
1012            let memory =
1013                loop_file.get_memory(locked, current_task, None, ProtectionFlags::READ).unwrap();
1014            let size = memory.get_content_size();
1015            let memory_contents = memory.read_to_vec(0, size).unwrap();
1016            assert_eq!(memory_contents, expected_contents);
1017        })
1018        .await;
1019    }
1020}