keos/
fs.rs

1//! Filesystem abstraction.
2
3/// Defines traits for file system operations.
4pub mod traits {
5    use alloc::{string::String, vec::Vec};
6
7    use super::{File, FileBlockNumber, InodeNumber};
8    use crate::{KernelError, mm::Page, sync::atomic::AtomicBool};
9
10    /// Trait representing a filesystem.
11    ///
12    /// This trait provides access to the root directory of the filesystem,
13    /// allowing operations on files and directories.
14    pub trait FileSystem
15    where
16        Self: Sync + Send,
17    {
18        /// Retrieves the root directory of the filesystem.
19        ///
20        /// # Returns
21        /// - `Some(Directory)`: A reference to the root directory if available.
22        /// - `None`: If the root directory is inaccessible or the filesystem is
23        ///   uninitialized.
24        fn root(&self) -> Option<super::Directory>;
25    }
26
27    /// Trait representing a regular file in the filesystem.
28    ///
29    /// A regular file contains user data and supports basic read and write
30    /// operations.
31    pub trait RegularFile
32    where
33        Self: Send + Sync,
34    {
35        /// Returns the inode number of the file.
36        fn ino(&self) -> InodeNumber;
37
38        /// Returns the size of the file in bytes.
39        fn size(&self) -> usize;
40
41        /// Reads data from the file into the provided buffer.
42        ///
43        /// # Parameters
44        /// - `fba`: The `FileBlockNumber` which to read.
45        /// - `buf`: A mutable array where the file content will be stored.
46        ///
47        /// # Returns
48        /// - `Ok(true)`: If the read success.
49        /// - `Ok(false)`: If the read success.
50        /// - `Err(Error)`: An error occured while the read operation.
51        fn read(&self, fba: FileBlockNumber, buf: &mut [u8; 4096]) -> Result<bool, KernelError>;
52
53        /// Writes a 4096-byte page of data into the specified file block.
54        ///
55        /// This method writes the contents of `buf` to the file block indicated
56        /// by `fba`. If the target block lies beyond the current end of
57        /// the file, the file may be extended up to `new_size` bytes to
58        /// accommodate the write.
59        ///
60        /// However, if the target block lies beyond the current file size
61        /// **and** `min_size` is insufficient to reach it, the write
62        /// will fail.
63        ///
64        /// # Parameters
65        /// - `fba`: The `FileBlockNumber` indicating the block to write to.
66        /// - `buf`: A buffer containing exactly 4096 bytes of data to write.
67        /// - `min_size`: The desired minimum file size (in bytes) after the
68        ///   write.   If this value is less than or equal to the current file
69        ///   size, no growth occurs.
70        ///
71        /// # Returns
72        /// - `Ok(())` if the write is successful.
73        /// - `Err(KernelError)` if the operation fails (e.g., out-of-bounds
74        ///   write, I/O error).
75        fn write(
76            &self,
77            fba: FileBlockNumber,
78            buf: &[u8; 4096],
79            min_size: usize,
80        ) -> Result<(), KernelError>;
81
82        /// Maps a file block into memory.
83        ///
84        /// This method retrieves the contents of the file at the specified file
85        /// block number (`fba`) and returns it as a [`Page`] containing the
86        /// contents of the file block.
87        ///
88        /// The memory-mapped page reflects the current contents of the file at
89        /// the requested block, and it can be used for reading or
90        /// modifying file data at page granularity.
91        ///
92        /// # Parameters
93        /// - `fba`: The file block number to map into memory. This is a logical
94        ///   offset into the file, measured in fixed-size blocks (not bytes).
95        ///
96        /// # Returns
97        /// - `Ok(Page)`: A reference-counted, in-memory page containing the
98        ///   file block's data.
99        /// - `Err(KernelError)`: If the file block cannot be found or loaded
100        ///   (e.g., out-of-bounds access).
101        fn mmap(&self, fba: FileBlockNumber) -> Result<Page, KernelError> {
102            let mut page = Page::new();
103            self.read(fba, page.inner_mut().as_mut_array().unwrap())?;
104            Ok(page)
105        }
106
107        /// Write back the file to disk.
108        fn writeback(&self) -> Result<(), KernelError>;
109    }
110
111    /// Trait representing a directory in the filesystem.
112    ///
113    /// A directory contains entries that reference other files or directories.
114    pub trait Directory
115    where
116        Self: Send + Sync,
117    {
118        /// Returns the inode number of the directory.
119        fn ino(&self) -> InodeNumber;
120
121        /// Returns the size of the file in bytes.
122        fn size(&self) -> usize;
123
124        /// Returns the link count of the directory.
125        fn link_count(&self) -> usize;
126
127        /// Opens an entry by name.
128        ///
129        /// # Parameters
130        /// - `entry`: The name of the entry to open.
131        ///
132        /// # Returns
133        /// - `Ok(File)`: The enumerate of the file (e.g., regular file,
134        ///   directory).
135        /// - `Err(Error)`: An error if the entry cannot be found or accessed.
136        fn open_entry(&self, entry: &str) -> Result<File, KernelError>;
137
138        /// Create an entry by name.
139        ///
140        /// # Parameters
141        /// - `entry`: The name of the entry to add.
142        /// - `is_dir`: Indicate whether the entry is directory or not.
143        ///
144        /// # Returns
145        /// - `Ok(())`: If the entry was successfully added.
146        /// - `Err(Error)`: An error if the add fails.
147        fn create_entry(&self, entry: &str, is_dir: bool) -> Result<File, KernelError>;
148
149        /// Unlinks a directory entry by name.
150        ///
151        /// # Parameters
152        /// - `entry`: The name of the entry to remove.
153        ///
154        /// # Returns
155        /// - `Ok(())`: If the entry was successfully removed.
156        /// - `Err(Error)`: An error if the removal fails.
157        fn unlink_entry(&self, entry: &str) -> Result<(), KernelError>;
158
159        /// Reads the contents of the directory.
160        ///
161        /// This function lists all the entries within the directory.
162        ///
163        /// # Returns
164        /// - `Ok(())`: If the directory was successfully read.
165        /// - `Err(Error)`: An error if the read operation fails.
166        fn read_dir(&self) -> Result<Vec<(InodeNumber, String)>, KernelError>;
167
168        /// Returns a reference of [`AtomicBool`] which contains whether
169        /// directory is removed.
170        ///
171        /// This is important because directory operations against the removed
172        /// directory will result in undesirable behavior (e.g. unreachable
173        /// file).
174        ///
175        /// # Returns
176        /// - `Ok(())`: If the directory was successfully read.
177        /// - `Err(Error)`: An error if the operation fails.
178        fn removed(&self) -> Result<&AtomicBool, KernelError>;
179    }
180}
181
182use crate::{KernelError, mm::Page, sync::atomic::AtomicBool};
183use alloc::{boxed::Box, string::String, sync::Arc, vec::Vec};
184use core::{iter::Step, num::NonZeroU32};
185
186/// A global file system abstraction.
187///
188/// The `FileSystem` struct provides an interface for interacting with the file
189/// system, including operations such as opening and creating files.
190///
191/// # Example
192/// ```
193/// let fs = file_system();
194/// if let Some(file) = fs.open("example.txt") {
195///     println!("Opened file: {}", file.name());
196/// }
197/// ```
198pub struct FileSystem {
199    _p: (),
200}
201
202static mut FS: Option<Box<dyn traits::FileSystem>> = None;
203
204impl FileSystem {
205    /// Retrieves the root directory of the filesystem.
206    ///
207    /// # Returns
208    /// - `Directory`: A reference to the root directory.
209    pub fn root() -> Directory {
210        unsafe { FS.as_ref() }
211            .and_then(|fs| fs.root())
212            .expect("Filesystem is not available.")
213    }
214
215    /// Register the global file system.
216    pub fn register(fs: impl traits::FileSystem + 'static) {
217        unsafe {
218            FS = Some(Box::new(fs));
219        }
220    }
221}
222
223/// A handle to a regular file.
224///
225/// This struct provides a reference-counted handle to a file that supports
226/// reading and writing operations at the kernel level.
227#[derive(Clone)]
228pub struct RegularFile(pub Arc<dyn traits::RegularFile>);
229
230impl RegularFile {
231    /// Inode number of the file.
232    pub fn ino(&self) -> InodeNumber {
233        self.0.ino()
234    }
235
236    /// Creates a new [`RegularFile`] handle from a given implementation of
237    /// [`traits::RegularFile`].
238    ///
239    /// This function takes an instance of any type that implements the
240    /// [`traits::RegularFile`] trait, wraps it in a reference-counted
241    /// [`Arc`], and returns a [`RegularFile`] handle.
242    ///
243    /// # Parameters
244    /// - `r`: An instance of a type that implements [`traits::RegularFile`].
245    ///
246    /// # Returns
247    /// A [`RegularFile`] handle that enables reference-counted access to the
248    /// underlying file.
249    pub fn new(r: impl traits::RegularFile + 'static) -> Self {
250        Self(Arc::new(r))
251    }
252
253    /// Returns the size of the file in bytes.
254    #[inline]
255    pub fn size(&self) -> usize {
256        self.0.size()
257    }
258
259    /// Reads data from the file into the provided buffer.
260    ///
261    /// # Parameters
262    /// - `fba`: The `FileBlockNumber` which to read.
263    /// - `buf`: A mutable slice where the file content will be stored.
264    ///
265    /// # Returns
266    /// - `Ok(usize)`: The number of bytes read.
267    /// - `Err(Error)`: An error if the read operation fails.
268    #[inline]
269    pub fn read(&self, mut position: usize, buf: &mut [u8]) -> Result<usize, KernelError> {
270        let mut bounce_buffer = alloc::boxed::Box::new([0; 4096]);
271        let max_read = self
272            .size()
273            .min(position + buf.len())
274            .saturating_sub(position);
275        let mut read_bytes = 0;
276        let first_segment = position & 0xfff;
277        if first_segment != 0 {
278            self.0.read(
279                FileBlockNumber::from_offset(position & !0xfff),
280                &mut bounce_buffer,
281            )?;
282            read_bytes += (0x1000 - first_segment).min(max_read);
283            buf[..read_bytes]
284                .copy_from_slice(&bounce_buffer[first_segment..first_segment + read_bytes]);
285            position += read_bytes;
286        }
287
288        for i in (read_bytes..max_read).step_by(0x1000) {
289            self.0
290                .read(FileBlockNumber::from_offset(position), &mut bounce_buffer)?;
291            let remainder = (max_read - i).min(0x1000);
292            buf[i..i + remainder].copy_from_slice(&bounce_buffer[..remainder]);
293            position += remainder;
294            read_bytes += remainder;
295        }
296        Ok(read_bytes)
297    }
298
299    /// Writes data from the buffer into the file.
300    ///
301    /// If the write position is beyond the current file size, file will be
302    /// extended to minimum size required to reflect the update.
303    ///
304    /// # Parameters
305    /// - `fba`: The `FileBlockNumber` which to write.
306    /// - `buf`: An slice containing the data to write.
307    ///
308    /// # Returns
309    /// - `Ok(usize)`: The number of bytes written.
310    /// - `Err(Error)`: An error if the write operation fails.
311    #[inline]
312    pub fn write(&self, mut position: usize, buf: &[u8]) -> Result<usize, KernelError> {
313        let mut bounce_buffer = alloc::boxed::Box::new([0; 4096]);
314        let mut write_bytes = 0;
315        let first_segment = position & 0xfff;
316        if first_segment != 0 {
317            let r = self.0.read(
318                FileBlockNumber::from_offset(position & !0xfff),
319                &mut bounce_buffer,
320            );
321            if matches!(
322                r,
323                Err(KernelError::IOError) | Err(KernelError::FilesystemCorrupted(_))
324            ) {
325                return r.map(|_| 0);
326            }
327            write_bytes += (0x1000 - first_segment).min(buf.len());
328            bounce_buffer[first_segment..first_segment + write_bytes]
329                .copy_from_slice(&buf[..write_bytes]);
330            self.0.write(
331                FileBlockNumber::from_offset(position & !0xfff),
332                &bounce_buffer,
333                position + write_bytes,
334            )?;
335            position += write_bytes;
336        }
337        for i in (write_bytes..buf.len()).step_by(0x1000) {
338            if buf.len() - i < 0x1000 {
339                break;
340            }
341            self.0.write(
342                FileBlockNumber::from_offset(position),
343                buf[i..i + 0x1000].as_array().unwrap(),
344                position + 0x1000,
345            )?;
346            position += 0x1000;
347            write_bytes += 0x1000;
348        }
349        if write_bytes != buf.len() {
350            let r = self
351                .0
352                .read(FileBlockNumber::from_offset(position), &mut bounce_buffer);
353            if matches!(
354                r,
355                Err(KernelError::IOError) | Err(KernelError::FilesystemCorrupted(_))
356            ) {
357                return r.map(|_| 0);
358            }
359            let remainder = buf.len() - write_bytes;
360            assert!(remainder < 0x1000);
361            bounce_buffer[..remainder].copy_from_slice(&buf[write_bytes..]);
362            self.0.write(
363                FileBlockNumber::from_offset(position),
364                &bounce_buffer,
365                position + remainder,
366            )?;
367            write_bytes += remainder;
368        }
369        Ok(write_bytes)
370    }
371
372    /// Maps a file block into memory.
373    ///
374    /// This method retrieves the contents of the file at the specified file
375    /// block number (`fba`) and returns it as a [`Page`] of the file
376    /// block.
377    ///
378    /// The memory-mapped page reflects the current contents of the file at
379    /// the requested block, and it can be used for reading or
380    /// modifying file data at page granularity.
381    ///
382    /// # Parameters
383    /// - `fba`: The file block number to map into memory. This is a logical
384    ///   offset into the file, measured in fixed-size blocks (not bytes).
385    ///
386    /// # Returns
387    /// - `Ok(Page)`: A reference-counted, in-memory page containing the file
388    ///   block's data.
389    /// - `Err(KernelError)`: If the file block cannot be found or loaded (e.g.,
390    ///   out-of-bounds access).
391    #[inline]
392    pub fn mmap(&self, fba: FileBlockNumber) -> Result<Page, KernelError> {
393        self.0.mmap(fba)
394    }
395
396    /// Write back the file to disk.
397    pub fn writeback(&self) -> Result<(), KernelError> {
398        self.0.writeback()
399    }
400}
401
402/// A handle to a directory.
403///
404/// This struct represents a reference-counted directory that supports
405/// file entry management, including opening and removing entries.
406#[derive(Clone)]
407pub struct Directory(pub Arc<dyn traits::Directory>);
408
409impl Directory {
410    /// Inode number of the directory.
411    pub fn ino(&self) -> InodeNumber {
412        self.0.ino()
413    }
414
415    /// Returns the size of the file in bytes.
416    #[inline]
417    pub fn size(&self) -> usize {
418        self.0.size()
419    }
420
421    /// Link count of the directory.
422    pub fn link_count(&self) -> usize {
423        self.0.link_count()
424    }
425
426    /// Creates a new [`Directory`] handle from a given implementation of
427    /// [`traits::Directory`].
428    ///
429    /// This function takes an instance of any type that implements the
430    /// [`traits::Directory`] trait, wraps it in a reference-counted
431    /// [`Arc`], and returns a [`Directory`] handle.
432    ///
433    /// # Parameters
434    /// - `r`: An instance of a type that implements [`traits::Directory`].
435    ///
436    /// # Returns
437    /// A [`Directory`] handle that enables reference-counted access to the
438    /// underlying file.
439    pub fn new(r: impl traits::Directory + 'static) -> Self {
440        Self(Arc::new(r))
441    }
442
443    /// Opens a path from the directory.
444    ///
445    /// # Parameters
446    /// - `path`: The path to the entry.
447    ///
448    /// # Returns
449    /// - `Ok(File)`: The type of the file (e.g., regular file, directory).
450    /// - `Err(Error)`: An error if the entry cannot be found or accessed.
451    #[inline]
452    pub fn open(&self, mut path: &str) -> Result<File, KernelError> {
453        let mut ret = File::Directory(if path.starts_with("/") {
454            path = &path[1..];
455            FileSystem::root()
456        } else {
457            self.clone()
458        });
459
460        for part in path.split("/").filter(|&s| !s.is_empty()) {
461            match ret {
462                File::Directory(d) => ret = d.0.open_entry(part)?,
463                File::RegularFile(_) => return Err(KernelError::NotDirectory),
464            }
465        }
466        Ok(ret)
467    }
468
469    /// Create an entry in the directory.
470    ///
471    /// # Parameters
472    /// - `path`: The path to the entry.
473    /// - `is_dir`: Indicate whether the entry is directory or not.
474    ///
475    /// # Returns
476    /// - `Ok(())`: If the entry was successfully added.
477    /// - `Err(Error)`: An error if the add fails.
478    #[inline]
479    pub fn create(&self, mut path: &str, is_dir: bool) -> Result<File, KernelError> {
480        let mut dstdir = if path.starts_with("/") {
481            path = &path[1..];
482            FileSystem::root()
483        } else {
484            self.clone()
485        };
486
487        let mut list: Vec<&str> = path.split("/").filter(|&s| !s.is_empty()).collect();
488        let entry = list.pop().ok_or(KernelError::InvalidArgument)?;
489
490        for part in list {
491            dstdir = dstdir
492                .0
493                .open_entry(part)?
494                .into_directory()
495                .ok_or(KernelError::NoSuchEntry)?;
496        }
497
498        dstdir.0.create_entry(entry, is_dir)
499    }
500
501    /// Unlink an entry in the directory.
502    ///
503    /// # Parameters
504    /// - `path`: The path to the entry.
505    ///
506    /// # Returns
507    /// - `Ok(())`: If the entry was successfully added.
508    /// - `Err(Error)`: An error if the add fails.
509    #[inline]
510    pub fn unlink(&self, mut path: &str) -> Result<(), KernelError> {
511        let mut dstdir = if path.starts_with("/") {
512            path = &path[1..];
513            FileSystem::root()
514        } else {
515            self.clone()
516        };
517
518        let mut list: Vec<&str> = path.split("/").filter(|&s| !s.is_empty()).collect();
519        let entry = list.pop().ok_or(KernelError::InvalidArgument)?;
520
521        for part in list {
522            dstdir = dstdir
523                .0
524                .open_entry(part)?
525                .into_directory()
526                .ok_or(KernelError::NoSuchEntry)?;
527        }
528
529        dstdir.0.unlink_entry(entry)
530    }
531
532    /// Reads the contents of the directory.
533    ///
534    /// This function lists all the entries within the directory.
535    ///
536    /// # Returns
537    /// - `Ok(())`: If the directory was successfully read.
538    /// - `Err(Error)`: An error if the read operation fails.
539    #[inline]
540    pub fn read_dir(&self) -> Result<Vec<(InodeNumber, String)>, KernelError> {
541        self.0.read_dir()
542    }
543
544    /// Returns [`AtomicBool`] which contains whether directory is removed.
545    ///
546    /// This is important because directory operations against the removed
547    /// directory will result in undesirable behavior (e.g. unreachable file).
548    ///
549    /// # Returns
550    /// - `Ok(())`: If the directory was successfully read.
551    /// - `Err(Error)`: An error if the operation fails.
552    #[inline]
553    pub fn removed(&self) -> Result<&AtomicBool, KernelError> {
554        self.0.removed()
555    }
556}
557
558/// Represents a file system entry, which can be either a file or a directory.
559///
560/// This enum allows distinguishing between regular files and directories within
561/// the filesystem. It provides flexibility for handling different file system
562/// objects in a unified manner.
563#[derive(Clone)]
564pub enum File {
565    /// A regular file.
566    ///
567    /// This variant represents a standard file in the filesystem, which can be
568    /// read from or written to.
569    RegularFile(RegularFile),
570
571    /// A directory.
572    ///
573    /// This variant represents a directory in the filesystem, which can contain
574    /// other files or directories.
575    Directory(Directory),
576}
577
578impl File {
579    /// Converts the [`File`] into a [`RegularFile`], if it is one.
580    ///
581    /// # Returns
582    ///
583    /// - `Some(RegularFile)` if `self` is a [`RegularFile`].
584    /// - `None` if `self` is not a `RegularFile`.
585    ///
586    /// This function allows extracting the [`RegularFile`] from [`File`]
587    /// safely.
588    pub fn into_regular_file(self) -> Option<RegularFile> {
589        if let File::RegularFile(r) = self {
590            Some(r)
591        } else {
592            None
593        }
594    }
595
596    /// Converts the `File` into a `Directory`, if it is one.
597    ///
598    /// # Returns
599    ///
600    /// - `Some(Directory)` if `self` is a `Directory`.
601    /// - `None` if `self` is not a `Directory`.
602    ///
603    /// This function allows extracting the `Directory` from `File` safely.
604    ///
605    /// # Example
606    ///
607    /// ```
608    /// let dir = File::Directory(directory);
609    /// assert!(dir.into_directory().is_some());
610    ///
611    /// let file = File::RegularFile(regular_file);
612    /// assert!(file.into_directory().is_none());
613    /// ```
614    pub fn into_directory(self) -> Option<Directory> {
615        if let File::Directory(d) = self {
616            Some(d)
617        } else {
618            None
619        }
620    }
621
622    /// Get [`InodeNumber`] of this [`File`] regardless of its inner type.
623    pub fn ino(&self) -> InodeNumber {
624        match self {
625            File::RegularFile(r) => r.ino(),
626            File::Directory(d) => d.ino(),
627        }
628    }
629
630    /// Get size of this [`File`] regardless of its inner type.
631    pub fn size(&self) -> u64 {
632        match self {
633            File::RegularFile(r) => r.size() as u64,
634            File::Directory(d) => d.size() as u64,
635        }
636    }
637}
638
639/// Sector, an access granuality for the disk.
640#[repr(transparent)]
641#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
642pub struct Sector(pub usize);
643
644impl Sector {
645    /// Get offset that represented by the sector.
646    #[inline]
647    pub fn into_offset(self) -> usize {
648        self.0 * 512
649    }
650
651    /// Cast into usize.
652    #[inline]
653    pub fn into_usize(self) -> usize {
654        self.0
655    }
656}
657
658impl core::ops::Add<usize> for Sector {
659    type Output = Self;
660
661    fn add(self, rhs: usize) -> Self {
662        Self(self.0 + rhs)
663    }
664}
665
666/// Represents a unique identifier for an inode in the filesystem.
667///
668/// An inode number uniquely identifies a file or directory within a filesystem.
669/// It is typically used to reference file metadata rather than file names.
670#[repr(transparent)]
671#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Debug)]
672pub struct InodeNumber(NonZeroU32);
673
674impl InodeNumber {
675    /// Creates a [`InodeNumber`] if the given value is not zero.
676    pub const fn new(n: u32) -> Option<Self> {
677        if let Some(v) = NonZeroU32::new(n) {
678            Some(Self(v))
679        } else {
680            None
681        }
682    }
683
684    /// Returns the contained value as a u32.
685    #[inline]
686    pub fn into_u32(&self) -> u32 {
687        self.0.get()
688    }
689}
690
691/// Represents a file block number within a file.
692///
693/// This number refers to the position of a block within a specific file.
694/// Each block contains 4096 bytes of contents.
695/// It helps in translating file-relative offsets to actual storage locations.
696#[repr(transparent)]
697#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Debug)]
698pub struct FileBlockNumber(pub usize);
699
700impl FileBlockNumber {
701    /// Computes the file block number from a byte offset within a file.
702    ///
703    /// In KeOS, each file is divided into file blocks of `0x1000` bytes (4
704    /// KiB). This function calculates the file block index corresponding to a
705    /// given byte offset.
706    ///
707    /// # Parameters
708    /// - `offset`: The byte offset within the file.
709    ///
710    /// # Returns
711    /// - The [`FileBlockNumber`] that corresponds to the given offset.
712    pub const fn from_offset(offset: usize) -> Self {
713        Self(offset / 0x1000)
714    }
715}
716
717impl Step for FileBlockNumber {
718    fn steps_between(start: &Self, end: &Self) -> (usize, Option<usize>) {
719        if start.0 <= end.0 {
720            let steps = end.0 - start.0;
721            (steps, Some(steps))
722        } else {
723            (0, None)
724        }
725    }
726
727    fn forward_checked(start: Self, count: usize) -> Option<Self> {
728        start.0.checked_add(count).map(Self)
729    }
730
731    fn backward_checked(start: Self, count: usize) -> Option<Self> {
732        start.0.checked_sub(count).map(Self)
733    }
734}
735
736impl core::ops::Add<usize> for FileBlockNumber {
737    type Output = Self;
738
739    fn add(self, rhs: usize) -> Self {
740        Self(self.0 + rhs)
741    }
742}
743
744// The type for disk hooking.
745#[doc(hidden)]
746pub type Hook =
747    Arc<dyn Fn(Sector, &[u8; 512], bool) -> Result<(), KernelError> + Send + Sync + 'static>;
748
749/// The disk, a device that has byte sink.
750///
751/// It gets slot number as its field.
752pub struct Disk {
753    index: usize,
754    is_ro: bool,
755    hook: Option<Hook>,
756}
757
758impl Disk {
759    /// Create a new FsDisk from the index.
760    pub fn new(index: usize) -> Self {
761        Self {
762            index,
763            is_ro: false,
764            hook: None,
765        }
766    }
767
768    /// Make the disk read-only.
769    pub fn ro(self) -> Self {
770        Self {
771            index: self.index,
772            is_ro: true,
773            hook: self.hook,
774        }
775    }
776
777    /// Add a hook for the disk.
778    pub fn hook(self, hook: Hook) -> Self {
779        Self {
780            index: self.index,
781            is_ro: self.is_ro,
782            hook: Some(hook),
783        }
784    }
785
786    /// Read 512 bytes from disk starting from sector.
787    pub fn read(&self, sector: Sector, buf: &mut [u8; 512]) -> Result<(), KernelError> {
788        let dev = abyss::dev::get_bdev(self.index).ok_or(KernelError::IOError)?;
789        if let Some(hook) = self.hook.as_ref() {
790            hook(sector, buf, false)?;
791        }
792        dev.read_bios(&mut Some((512 * sector.into_usize(), buf.as_mut())).into_iter())
793            .map_err(|_| KernelError::IOError)
794    }
795
796    /// Write 512 bytes to disk starting from sector.
797    pub fn write(&self, sector: Sector, buf: &[u8; 512]) -> Result<(), KernelError> {
798        let dev = abyss::dev::get_bdev(self.index).ok_or(KernelError::IOError)?;
799        if self.is_ro {
800            Err(KernelError::NotSupportedOperation)
801        } else {
802            if let Some(hook) = self.hook.as_ref() {
803                hook(sector, buf, true)?;
804            }
805            dev.write_bios(&mut Some((512 * sector.into_usize(), buf.as_ref())).into_iter())
806                .map_err(|_| KernelError::IOError)
807        }
808    }
809}