efs/fs/ext2/
directory.rs

1//! Interface with ext2's directories.
2//!
3//! See the [OSdev wiki](https://wiki.osdev.org/Ext2#Directories) and the [*The Second Extended Filesystem* book](https://www.nongnu.org/ext2-doc/ext2.html#directory) for more information.
4
5use alloc::ffi::CString;
6use alloc::vec::Vec;
7use core::fmt::Debug;
8
9use deku::{DekuError, DekuRead, DekuWrite};
10
11use super::Ext2;
12use super::error::Ext2Error;
13use crate::arch::u32_to_usize;
14use crate::dev::Device;
15use crate::dev::address::Address;
16use crate::error::Error;
17use crate::fs::file::Type;
18
19/// A directory entry.
20#[derive(Debug, Clone, DekuRead, DekuWrite)]
21#[deku(endian = "little")]
22pub struct Entry {
23    /// Inode index.
24    pub inode: u32,
25
26    /// Total size of this entry (including all headers and the name).
27    pub rec_len: u16,
28
29    /// Name Length least-significant 8 bits.
30    pub name_len: u8,
31
32    /// Type indicator (only if the feature bit for "directory entries have file type byte" is set, else this is the
33    /// most-significant 8 bits of the Name Length).
34    pub file_type: u8,
35
36    /// Name of the directory entry.
37    #[deku(
38        bytes_read = "*name_len",
39        map = "|bytes: Vec<u8>| -> Result<_, DekuError> { CString::new(bytes).map_err(|_| DekuError::Io(deku::no_std_io::ErrorKind::InvalidData)) }"
40    )]
41    pub name: CString,
42}
43
44/// File type indicated in a directory entry.
45///
46/// See the [*The Second Extended Filesystem* book](https://www.nongnu.org/ext2-doc/ext2.html#ifdir-file-type) for more information.
47#[repr(u8)]
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49pub enum FileType {
50    /// Unknown file type.
51    Unknown = 0,
52
53    /// Regular file.
54    RegFile = 1,
55
56    /// Directory.
57    Dir = 2,
58
59    /// Character device.
60    ChrDev = 3,
61
62    /// Block device.
63    BlkDev = 4,
64
65    /// FIFO.
66    Fifo = 5,
67
68    /// UNIX socket.
69    Sock = 6,
70
71    /// Symbolic link
72    Symlink = 7,
73}
74
75impl From<u8> for FileType {
76    fn from(value: u8) -> Self {
77        match value {
78            1 => Self::RegFile,
79            2 => Self::Dir,
80            3 => Self::ChrDev,
81            4 => Self::BlkDev,
82            5 => Self::Fifo,
83            6 => Self::Sock,
84            7 => Self::Symlink,
85            _ => Self::Unknown,
86        }
87    }
88}
89
90impl From<FileType> for u8 {
91    fn from(value: FileType) -> Self {
92        value as Self
93    }
94}
95
96impl From<Type> for FileType {
97    fn from(value: Type) -> Self {
98        match value {
99            Type::Regular => Self::RegFile,
100            Type::Directory => Self::Dir,
101            Type::SymbolicLink => Self::Symlink,
102            Type::Fifo => Self::Fifo,
103            Type::CharacterDevice => Self::ChrDev,
104            Type::BlockDevice => Self::BlkDev,
105            Type::Socket => Self::Sock,
106        }
107    }
108}
109
110impl TryFrom<FileType> for Type {
111    type Error = Ext2Error;
112
113    fn try_from(value: FileType) -> Result<Self, Self::Error> {
114        match value {
115            FileType::Unknown => Err(Ext2Error::UnknownEntryFileType),
116            FileType::RegFile => Ok(Self::Regular),
117            FileType::Dir => Ok(Self::Directory),
118            FileType::ChrDev => Ok(Self::CharacterDevice),
119            FileType::BlkDev => Ok(Self::BlockDevice),
120            FileType::Fifo => Ok(Self::Fifo),
121            FileType::Sock => Ok(Self::Socket),
122            FileType::Symlink => Ok(Self::SymbolicLink),
123        }
124    }
125}
126
127impl Entry {
128    /// Returns the directory entry starting at the given address.
129    ///
130    /// # Errors
131    ///
132    /// Returns an [`Ext2Error::BadString`] if the name of the entry is not a valid C-string (non-null terminated).
133    ///
134    /// Returns an [`Error::IO`] if the device cannot be read.
135    pub fn parse<Dev: Device>(fs: &Ext2<Dev>, starting_addr: Address) -> Result<Self, Error<Ext2Error>> {
136        let mut device = fs.device.lock();
137        device
138            .read_from_bytes(starting_addr, u32_to_usize(fs.superblock().block_size()))
139            .map_err(Into::into)
140    }
141
142    /// Returns the minimal size in bytes that this entry could take (with no consideration for `rec_len`).
143    ///
144    /// # Panics
145    ///
146    /// Cannot panic on an entry obtained with [`parse`](struct.Entry.html#method.parse): can only panic by creating by
147    /// hand a ill-formed directory entry (whose length is greater than [`u16::MAX`]).
148    #[must_use]
149    pub fn minimal_size(&self) -> u16 {
150        let minimal_size = u16::try_from(8 + self.name.to_bytes_with_nul().len()).expect("Ill-formed directory entry");
151        minimal_size + (4 - ((minimal_size - 1) % 4 + 1))
152    }
153
154    /// Returns the free space contained in this entry.
155    ///
156    /// # Panics
157    ///
158    /// Cannot panic on an entry obtained with [`parse`](struct.Entry.html#method.parse): can only panic by creating by
159    /// hand a ill-formed directory entry (whose length is greater than [`u16::MAX`]).
160    #[must_use]
161    pub fn free_space(&self) -> u16 {
162        self.rec_len - u16::try_from(8 + self.name.to_bytes_with_nul().len()).expect("Ill-formed directory entry")
163    }
164
165    /// Returns this entry as bytes.
166    #[must_use]
167    pub fn as_bytes(&self) -> Vec<u8> {
168        let mut bytes = Vec::new();
169
170        bytes.append(&mut self.inode.to_le_bytes().to_vec());
171        bytes.append(&mut self.rec_len.to_le_bytes().to_vec());
172        bytes.push(self.name_len);
173        bytes.push(self.file_type);
174        bytes.append(&mut self.name.to_bytes_with_nul().to_vec());
175
176        bytes
177    }
178}