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