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