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, get_sysfs};
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, FileToken, FsNodeOps, FsString,
20    InputBufferCallback, NamespaceNode, PeekBufferSegmentsCallback, 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::{LockDepMutex, LoopDeviceStateLock, LoopDevicesLock};
26use starnix_syscalls::{SUCCESS, SyscallArg, SyscallResult};
27use starnix_types::user_buffer::UserBuffer;
28use starnix_uapi::device_id::{DeviceId, 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        current_task: &CurrentTask,
98        device: &Arc<LoopDevice>,
99        backing_file: FileHandle,
100    ) -> Result<(), Errno> {
101        if self.backing_file.is_some() {
102            return error!(EBUSY);
103        }
104        let backing_stat = backing_file.node().stat(current_task)?;
105        self.backing_file = Some(backing_file);
106        self.size_limit = backing_stat.st_size as u64;
107        if let Some(sysfs_dir) = self.k_device.as_ref().and_then(Device::dir) {
108            let device_weak = Arc::downgrade(device);
109            sysfs_dir.edit(&get_sysfs(current_task.kernel()), |dir| {
110                dir.subdir("loop", 0o755, |dir| {
111                    dir.entry(
112                        "backing_file",
113                        LoopDeviceBackingFile::new_node(device_weak),
114                        mode!(IFREG, 0o644),
115                    );
116                });
117            });
118        }
119        Ok(())
120    }
121
122    fn set_info(&mut self, info: &uapi::loop_info64) {
123        let encrypt_key_size = info.lo_encrypt_key_size.clamp(0, LO_KEY_SIZE);
124        self.offset = info.lo_offset;
125        self.size_limit = info.lo_sizelimit;
126        self.flags = LoopDeviceFlags::from_bits_truncate(info.lo_flags);
127        self.encrypt_type = info.lo_encrypt_type;
128        self.encrypt_key = info.lo_encrypt_key[0..(encrypt_key_size as usize)].to_owned();
129        self.init = info.lo_init;
130    }
131
132    fn update_size_limit(&mut self, current_task: &CurrentTask) -> Result<(), Errno> {
133        if let Some(backing_file) = &self.backing_file {
134            let backing_stat = backing_file.node().stat(current_task)?;
135            self.size_limit = backing_stat.st_size as u64;
136        }
137        Ok(())
138    }
139
140    fn set_k_device(&mut self, k_device: Device) {
141        self.k_device = Some(k_device);
142    }
143
144    fn clear(&mut self) {
145        if let Some(sysfs_dir) = self.k_device.as_ref().and_then(Device::dir) {
146            sysfs_dir.remove(b"loop".into());
147        }
148        *self = Self { k_device: self.k_device.take(), ..Default::default() };
149    }
150}
151
152#[derive(Debug, Default)]
153struct LoopDevice {
154    number: u32,
155    state: LockDepMutex<LoopDeviceState, LoopDeviceStateLock>,
156}
157
158struct LoopDeviceBackingFile(Weak<LoopDevice>);
159
160impl LoopDeviceBackingFile {
161    pub fn new_node(device: Weak<LoopDevice>) -> impl FsNodeOps {
162        BytesFile::new_node(Self(device))
163    }
164}
165
166impl BytesFileOps for LoopDeviceBackingFile {
167    fn read(&self, current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
168        let mut path = self
169            .0
170            .upgrade()
171            .ok_or_else(|| errno!(EINVAL))?
172            .state
173            .lock()
174            .backing_file
175            .as_ref()
176            .ok_or_else(|| errno!(EINVAL))
177            .map(|file| file.name.to_passive().path(&current_task.fs()))?;
178        path.push(b'\n');
179        Ok(Cow::Owned(path.into()))
180    }
181}
182
183impl LoopDevice {
184    fn new<'a>(kernel: &Kernel, minor: u32) -> Result<Arc<Self>, Errno> {
185        let registry = &kernel.device_registry;
186        let loop_device_name = FsString::from(format!("loop{minor}"));
187        let block_class = registry.objects.block_class();
188        let device = Arc::new(Self { number: minor, state: Default::default() });
189        let device_weak = Arc::<LoopDevice>::downgrade(&device);
190        let k_device = registry.add_major_device(
191            kernel,
192            loop_device_name.as_ref(),
193            DeviceMetadata::new(
194                loop_device_name.clone(),
195                DeviceId::new(LOOP_MAJOR, minor),
196                DeviceMode::Block,
197            )
198            // It is not generally true that all loop devices are disks, but it is true for
199            // the ones we currently support. Future work should allow us to set this dynamically.
200            .with_devtype("disk"),
201            /* parent = */ None,
202            block_class,
203            |k_device, dir| build_block_device_directory(k_device, device_weak, dir),
204        );
205        {
206            let mut state = device.state.lock();
207            state.set_k_device(k_device);
208        }
209        Ok(device)
210    }
211
212    fn create_file_ops(self: &Arc<Self>) -> Box<dyn FileOps> {
213        Box::new(LoopDeviceFile { device: self.clone() })
214    }
215
216    fn backing_file(&self) -> Option<FileHandle> {
217        self.state.lock().backing_file.clone()
218    }
219
220    fn is_bound(&self) -> bool {
221        self.state.lock().backing_file.is_some()
222    }
223
224    fn offset_for_backing_file(&self, offset: usize) -> usize {
225        self.state.lock().offset.saturating_add(offset as u64) as usize
226    }
227}
228
229fn check_block_size(block_size: u32) -> Result<(), Errno> {
230    let page_size = *PAGE_SIZE as u32;
231    let mut allowed_size = MIN_BLOCK_SIZE;
232    while allowed_size <= page_size {
233        if block_size == allowed_size {
234            return Ok(());
235        }
236        allowed_size *= 2;
237    }
238    error!(EINVAL)
239}
240
241impl BlockDeviceInfo for LoopDevice {
242    fn size(&self) -> Result<usize, Errno> {
243        Ok(self.state.lock().size_limit as usize)
244    }
245}
246
247#[derive(Debug)]
248struct CroppedInputBuffer<'a> {
249    base: &'a mut dyn InputBuffer,
250    size: usize,
251    drained: bool,
252}
253
254impl<'a> CroppedInputBuffer<'a> {
255    fn new(base: &'a mut dyn InputBuffer, size: usize) -> Self {
256        debug_assert!(size <= base.bytes_read() + base.available());
257        CroppedInputBuffer { base, size, drained: false }
258    }
259}
260
261impl<'a> Buffer for CroppedInputBuffer<'a> {
262    fn segments_count(&self) -> Result<usize, Errno> {
263        error!(ENOTSUP)
264    }
265
266    fn peek_each_segment(
267        &mut self,
268        callback: &mut PeekBufferSegmentsCallback<'_>,
269    ) -> Result<(), Errno> {
270        let mut pos = 0;
271        self.base.peek_each_segment(&mut |buffer: &UserBuffer| {
272            if pos >= self.size {
273                return;
274            } else if pos + buffer.length > self.size {
275                let cropped_size = self.size - pos;
276                pos += buffer.length;
277                callback(&UserBuffer { address: buffer.address, length: cropped_size });
278            } else {
279                pos += buffer.length;
280                callback(buffer);
281            }
282        })
283    }
284}
285
286impl<'a> InputBuffer for CroppedInputBuffer<'a> {
287    fn peek_each(&mut self, callback: &mut InputBufferCallback<'_>) -> Result<usize, Errno> {
288        if self.drained {
289            return Ok(0);
290        }
291        let mut pos = self.base.bytes_read();
292        self.base.peek_each(&mut |buf: &[u8]| {
293            if pos >= self.size {
294                return Ok(0);
295            }
296            let size = std::cmp::min(buf.len(), self.size - pos);
297            pos += size;
298            callback(&buf[..size])
299        })
300    }
301    fn advance(&mut self, length: usize) -> Result<(), Errno> {
302        if length > self.available() {
303            return error!(EINVAL);
304        }
305        self.base.advance(length)
306    }
307    fn available(&self) -> usize {
308        if self.drained || self.size < self.bytes_read() {
309            0
310        } else {
311            self.size - self.bytes_read()
312        }
313    }
314    fn bytes_read(&self) -> usize {
315        self.base.bytes_read()
316    }
317    fn drain(&mut self) -> usize {
318        let size = self.available();
319        self.drained = true;
320        size
321    }
322}
323
324struct LoopDeviceFile {
325    device: Arc<LoopDevice>,
326}
327
328impl FileOps for LoopDeviceFile {
329    fileops_impl_seekable!();
330
331    fn read(
332        &self,
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(current_task, self.device.offset_for_backing_file(offset), data)
340        } else {
341            Ok(0)
342        }
343    }
344
345    fn write(
346        &self,
347        _file: &FileObject,
348        current_task: &CurrentTask,
349        offset: usize,
350        data: &mut dyn InputBuffer,
351    ) -> Result<usize, Errno> {
352        if let Some(backing_file) = self.device.backing_file() {
353            let limit = self.device.state.lock().size_limit as usize;
354            if offset >= limit {
355                // Can't write past the size limit.
356                return Ok(0);
357            }
358            let mut cropped_buf;
359            let data = if offset + data.available() > limit {
360                // If the write would exceed the size limit, then crop the input buffer to write
361                // to the limit without exceeding it.
362                let bytes_to_write = limit - offset;
363                let cropped_size = data.bytes_read() + bytes_to_write;
364                cropped_buf = CroppedInputBuffer::new(data, cropped_size);
365                &mut cropped_buf
366            } else {
367                data
368            };
369            let r = backing_file.write_at(
370                current_task,
371                self.device.offset_for_backing_file(offset),
372                data,
373            );
374            r
375        } else {
376            error!(ENOSPC)
377        }
378    }
379
380    fn get_memory(
381        &self,
382        _file: &FileObject,
383        current_task: &CurrentTask,
384        requested_length: Option<usize>,
385        prot: ProtectionFlags,
386    ) -> Result<Arc<MemoryObject>, Errno> {
387        let backing_file = self.device.backing_file().ok_or_else(|| errno!(EBADF))?;
388
389        let state = self.device.state.lock();
390        let configured_offset = state.offset;
391        let configured_size_limit = match state.size_limit {
392            // If the size limit is 0, use all available bytes from the backing file.
393            0 => None,
394            n => Some(n),
395        };
396
397        let backing_memory = backing_file.get_memory(
398            current_task,
399            requested_length.map(|l| l + configured_offset as usize),
400            prot,
401        )?;
402        let backing_memory_size = backing_memory.get_size();
403
404        let clone_len = backing_memory_size
405            .min(configured_size_limit.unwrap_or(u64::MAX))
406            .min(requested_length.unwrap_or(usize::MAX) as u64);
407
408        let backing_content_size = backing_memory.get_content_size();
409
410        let mem = if backing_content_size < clone_len {
411            // If we need to set a content size then the backing memory must not be writable since
412            // we are going to create a snapshot clone, which will prevent any writes from becoming
413            // visible in the backing memory.
414            if backing_file.can_write() {
415                track_stub!(
416                    TODO("https://fxbug.dev/408048145"),
417                    "Loop device mutable loop backing files with smaller content"
418                );
419                return error!(EINVAL);
420            }
421            let memory_clone = backing_memory
422                .create_child(
423                    VmoChildOptions::SNAPSHOT_AT_LEAST_ON_WRITE,
424                    configured_offset,
425                    clone_len,
426                )
427                .map_err(|e| errno!(EINVAL, e))?;
428            let new_content_size = backing_content_size.saturating_sub(configured_offset);
429            memory_clone.set_content_size(new_content_size).map_err(|e| errno!(EINVAL, e))?;
430            memory_clone
431        } else {
432            backing_memory
433                .create_child(VmoChildOptions::SLICE, configured_offset, clone_len)
434                .map_err(|e| errno!(EINVAL, e))?
435        };
436        Ok(Arc::new(mem))
437    }
438
439    fn get_file_token(
440        &self,
441        _file: &FileObject,
442        current_task: &CurrentTask,
443    ) -> Result<FileToken, Errno> {
444        let backing_file = self.device.backing_file().ok_or_else(|| errno!(EBADF))?;
445        let configured_offset = self.device.state.lock().offset;
446        backing_file.get_file_token(current_task)?.add_offset(configured_offset)
447    }
448
449    fn sync(&self, _file: &FileObject, current_task: &CurrentTask) -> Result<(), Errno> {
450        if let Some(f) = self.device.backing_file() { f.sync(current_task) } else { Ok(()) }
451    }
452
453    fn ioctl(
454        &self,
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(current_task, &self.device, backing_file)?;
492                Ok(SUCCESS)
493            }
494            LOOP_CLR_FD => {
495                let mut state = self.device.state.lock();
496                state.check_bound()?;
497                state.clear();
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(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(current_task, &self.device, 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            _ => error!(ENOTTY),
624        }
625    }
626}
627
628pub fn loop_device_init(kernel: &Kernel) -> Result<(), Errno> {
629    // Device registry.
630    kernel
631        .device_registry
632        .register_major("loop".into(), DeviceMode::Block, LOOP_MAJOR, get_or_create_loop_device)
633        .expect("loop device register failed.");
634
635    // Ensure initial loop devices.
636    kernel.expando.get::<LoopDeviceRegistry>().ensure_initial_devices(kernel)
637}
638
639#[derive(Debug, Default)]
640pub struct LoopDeviceRegistry {
641    devices: LockDepMutex<BTreeMap<u32, Arc<LoopDevice>>, LoopDevicesLock>,
642}
643
644impl LoopDeviceRegistry {
645    /// Ensure initial loop devices.
646    fn ensure_initial_devices(&self, kernel: &Kernel) -> Result<(), Errno> {
647        for minor in 0..8 {
648            self.get_or_create(kernel, minor)?;
649        }
650        Ok(())
651    }
652
653    #[cfg(test)]
654    fn get(&self, minor: u32) -> Result<Arc<LoopDevice>, Errno> {
655        self.devices.lock().get(&minor).ok_or_else(|| errno!(ENODEV)).cloned()
656    }
657
658    fn get_or_create<'a>(&self, kernel: &Kernel, minor: u32) -> Result<Arc<LoopDevice>, Errno> {
659        self.devices
660            .lock()
661            .entry(minor)
662            .or_insert_with_fallible(|| LoopDevice::new(kernel, minor))
663            .cloned()
664    }
665
666    fn find(&self, current_task: &CurrentTask) -> Result<u32, Errno> {
667        let mut devices = self.devices.lock();
668        for minor in 0..u32::MAX {
669            match devices.entry(minor) {
670                Entry::Vacant(e) => {
671                    e.insert(LoopDevice::new(current_task.kernel(), minor)?);
672                    return Ok(minor);
673                }
674                Entry::Occupied(e) => {
675                    if !e.get().is_bound() {
676                        return Ok(minor);
677                    }
678                }
679            }
680        }
681        error!(ENODEV)
682    }
683
684    fn add(&self, current_task: &CurrentTask, minor: u32) -> Result<(), Errno> {
685        match self.devices.lock().entry(minor) {
686            Entry::Vacant(e) => {
687                e.insert(LoopDevice::new(current_task.kernel(), minor)?);
688                Ok(())
689            }
690            Entry::Occupied(_) => {
691                error!(EEXIST)
692            }
693        }
694    }
695
696    fn remove(&self, current_task: &CurrentTask, minor: u32) -> Result<(), Errno> {
697        match self.devices.lock().entry(minor) {
698            Entry::Vacant(_) => error!(ENODEV),
699            Entry::Occupied(e) => {
700                let dev = {
701                    let mut state = e.get().state.lock();
702                    if state.backing_file.is_some() {
703                        return error!(EBUSY);
704                    }
705                    state.k_device.take().ok_or_else(|| errno!(EINVAL))?
706                };
707                e.remove();
708                let kernel = current_task.kernel();
709                let registry = &kernel.device_registry;
710                registry.remove_device(current_task, dev);
711                Ok(())
712            }
713        }
714    }
715}
716
717pub fn create_loop_control_device(
718    current_task: &CurrentTask,
719    _id: DeviceId,
720    _node: &NamespaceNode,
721    _flags: OpenFlags,
722) -> Result<Box<dyn FileOps>, Errno> {
723    Ok(Box::new(LoopControlDevice::new(current_task.kernel().expando.get::<LoopDeviceRegistry>())))
724}
725
726struct LoopControlDevice {
727    registry: Arc<LoopDeviceRegistry>,
728}
729
730impl LoopControlDevice {
731    pub fn new(registry: Arc<LoopDeviceRegistry>) -> Self {
732        Self { registry }
733    }
734}
735
736impl FileOps for LoopControlDevice {
737    fileops_impl_seekless!();
738    fileops_impl_dataless!();
739    fileops_impl_noop_sync!();
740
741    fn ioctl(
742        &self,
743        _file: &FileObject,
744        current_task: &CurrentTask,
745        request: u32,
746        arg: SyscallArg,
747    ) -> Result<SyscallResult, Errno> {
748        match request {
749            LOOP_CTL_GET_FREE => Ok(self.registry.find(current_task)?.into()),
750            LOOP_CTL_ADD => {
751                let minor = arg.into();
752                let registry = Arc::clone(&self.registry);
753                // Delegate to the system task to have the permission to create the loop device.
754                let closure = move |task: &CurrentTask| registry.add(task, minor);
755                let (result, req) = SpawnRequestBuilder::new()
756                    .with_debug_name("loop-control-add")
757                    .with_sync_closure(closure)
758                    .build_with_sync_result();
759                current_task.kernel().kthreads.spawner().spawn_from_request(req);
760
761                result()??;
762
763                Ok(minor.into())
764            }
765            LOOP_CTL_REMOVE => {
766                let minor = arg.into();
767                self.registry.remove(current_task, minor)?;
768                Ok(minor.into())
769            }
770            _ => error!(ENOTTY),
771        }
772    }
773}
774
775fn get_or_create_loop_device(
776    current_task: &CurrentTask,
777    id: DeviceId,
778    _node: &NamespaceNode,
779    _flags: OpenFlags,
780) -> Result<Box<dyn FileOps>, Errno> {
781    Ok(current_task
782        .kernel()
783        .expando
784        .get::<LoopDeviceRegistry>()
785        .get_or_create(current_task.kernel(), id.minor())?
786        .create_file_ops())
787}
788
789#[cfg(test)]
790mod tests {
791    use super::*;
792    use fidl::endpoints::Proxy;
793    use fidl_fuchsia_io as fio;
794    use starnix_core::fs::fuchsia::new_remote_file;
795    use starnix_core::testing::*;
796    use starnix_core::vfs::buffers::*;
797    use starnix_core::vfs::pseudo::dynamic_file::{DynamicFile, DynamicFileBuf, DynamicFileSource};
798    use starnix_core::vfs::{FdFlags, FsNodeOps};
799
800    #[derive(Clone)]
801    struct PassthroughTestFile(Vec<u8>);
802
803    impl PassthroughTestFile {
804        pub fn new_node(bytes: &[u8]) -> impl FsNodeOps {
805            DynamicFile::new_node(Self(bytes.to_owned()))
806        }
807    }
808
809    impl DynamicFileSource for PassthroughTestFile {
810        fn generate(
811            &self,
812            _current_task: &CurrentTask,
813            sink: &mut DynamicFileBuf,
814        ) -> Result<(), Errno> {
815            sink.write(&self.0);
816            Ok(())
817        }
818    }
819
820    fn bind_simple_loop_device(
821        current_task: &CurrentTask,
822        backing_file: FileHandle,
823        open_flags: OpenFlags,
824    ) -> FileHandle {
825        let backing_fd = current_task.add_file(backing_file, FdFlags::empty()).unwrap();
826
827        let loop_file = anon_test_file(
828            &current_task,
829            Box::new(LoopDeviceFile { device: Arc::new(LoopDevice::default()) }),
830            open_flags,
831        );
832
833        let config_addr = map_object_anywhere(
834            &current_task,
835            &uapi::loop_config {
836                block_size: MIN_BLOCK_SIZE,
837                fd: backing_fd.raw() as u32,
838                ..Default::default()
839            },
840        );
841        loop_file.ioctl(&current_task, LOOP_CONFIGURE, config_addr.into()).unwrap();
842
843        loop_file
844    }
845
846    #[::fuchsia::test]
847    async fn basic_read() {
848        spawn_kernel_and_run(async |current_task| {
849            let fs = create_testfs(&current_task.kernel());
850            let expected_contents = b"hello, world!";
851
852            let ops = PassthroughTestFile::new_node(expected_contents);
853            let backing_node = create_fs_node_for_testing(&fs, ops);
854            let file_ops = backing_node.create_file_ops(current_task, OpenFlags::RDONLY).unwrap();
855            let backing_file = anon_test_file(current_task, file_ops, OpenFlags::RDONLY);
856            let loop_file = bind_simple_loop_device(current_task, backing_file, OpenFlags::RDONLY);
857
858            let mut buf = VecOutputBuffer::new(expected_contents.len());
859            loop_file.read(current_task, &mut buf).unwrap();
860
861            assert_eq!(buf.data(), expected_contents);
862        })
863        .await;
864    }
865
866    #[::fuchsia::test]
867    async fn offset_works() {
868        spawn_kernel_and_run(async |current_task| {
869            let fs = create_testfs(&current_task.kernel());
870            let ops = PassthroughTestFile::new_node(b"hello, world!");
871            let backing_node = create_fs_node_for_testing(&fs, ops);
872            let file_ops = backing_node.create_file_ops(current_task, OpenFlags::RDONLY).unwrap();
873            let backing_file = anon_test_file(current_task, file_ops, OpenFlags::RDONLY);
874            let loop_file = bind_simple_loop_device(current_task, backing_file, OpenFlags::RDONLY);
875
876            let info_addr = map_object_anywhere(
877                current_task,
878                &uapi::loop_info64 { lo_offset: 3, ..Default::default() },
879            );
880            loop_file.ioctl(current_task, LOOP_SET_STATUS64, info_addr.into()).unwrap();
881
882            let mut buf = VecOutputBuffer::new(25);
883            loop_file.read(current_task, &mut buf).unwrap();
884
885            assert_eq!(buf.data(), b"lo, world!");
886        })
887        .await;
888    }
889
890    #[::fuchsia::test]
891    async fn basic_get_memory() {
892        let test_data_path = "/pkg/data/testfile.txt";
893        let expected_contents = std::fs::read(test_data_path).unwrap();
894
895        let txt_channel: zx::Channel =
896            fuchsia_fs::file::open_in_namespace(test_data_path, fio::PERM_READABLE)
897                .unwrap()
898                .into_channel()
899                .unwrap()
900                .into();
901
902        spawn_kernel_and_run(async move |current_task| {
903            let backing_file =
904                new_remote_file(current_task, txt_channel.into(), OpenFlags::RDONLY).unwrap();
905            let loop_file = bind_simple_loop_device(current_task, backing_file, OpenFlags::RDONLY);
906
907            let memory = loop_file.get_memory(current_task, None, ProtectionFlags::READ).unwrap();
908            let size = memory.get_content_size();
909            let memory_contents = memory.read_to_vec(0, size).unwrap();
910            assert_eq!(memory_contents, expected_contents);
911        })
912        .await;
913    }
914
915    #[::fuchsia::test]
916    async fn get_memory_offset_and_size_limit_work() {
917        // VMO slice children require a page-aligned offset, so we need a file that's big enough to
918        // have multiple pages to support creating a child with a meaningful offset, our own
919        // binary should do the trick.
920        let test_data_path = std::env::args().next().unwrap();
921        let expected_offset = *PAGE_SIZE;
922        let expected_size_limit = *PAGE_SIZE;
923        let expected_contents = std::fs::read(&test_data_path).unwrap();
924        let expected_contents = expected_contents
925            [expected_offset as usize..(expected_offset + expected_size_limit) as usize]
926            .to_vec();
927
928        let txt_channel: zx::Channel =
929            fuchsia_fs::file::open_in_namespace(&test_data_path, fio::PERM_READABLE)
930                .unwrap()
931                .into_channel()
932                .unwrap()
933                .into();
934
935        spawn_kernel_and_run(async move |current_task| {
936            let backing_file =
937                new_remote_file(current_task, txt_channel.into(), OpenFlags::RDONLY).unwrap();
938            let loop_file = bind_simple_loop_device(current_task, backing_file, OpenFlags::RDONLY);
939
940            let info_addr = map_object_anywhere(
941                current_task,
942                &uapi::loop_info64 {
943                    lo_offset: expected_offset,
944                    lo_sizelimit: expected_size_limit,
945                    ..Default::default()
946                },
947            );
948            loop_file.ioctl(current_task, LOOP_SET_STATUS64, info_addr.into()).unwrap();
949
950            let memory = loop_file.get_memory(current_task, None, ProtectionFlags::READ).unwrap();
951            let size = memory.get_content_size();
952            let memory_contents = memory.read_to_vec(0, size).unwrap();
953            assert_eq!(memory_contents, expected_contents);
954        })
955        .await;
956    }
957
958    #[::fuchsia::test]
959    async fn remove_without_k_device_preserves_registry_entry() {
960        spawn_kernel_and_run(async |current_task| {
961            let registry = LoopDeviceRegistry::default();
962            let minor = 42;
963            registry
964                .devices
965                .lock()
966                .insert(minor, Arc::new(LoopDevice { number: minor, state: Default::default() }));
967
968            assert_eq!(registry.remove(current_task, minor), error!(EINVAL));
969            assert!(registry.get(minor).is_ok());
970        })
971        .await;
972    }
973}