ext4fs/ondisk/
dir_entry.rs1#![forbid(unsafe_code)]
2use crate::error::{Ext4Error, Result};
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum DirEntryType {
7 Unknown,
8 RegularFile,
9 Directory,
10 CharDevice,
11 BlockDevice,
12 Fifo,
13 Socket,
14 Symlink,
15}
16
17impl From<u8> for DirEntryType {
18 fn from(v: u8) -> Self {
19 match v {
20 1 => Self::RegularFile,
21 2 => Self::Directory,
22 3 => Self::CharDevice,
23 4 => Self::BlockDevice,
24 5 => Self::Fifo,
25 6 => Self::Socket,
26 7 => Self::Symlink,
27 _ => Self::Unknown,
28 }
29 }
30}
31
32#[derive(Debug, Clone)]
41pub struct DirEntry {
42 pub inode: u32,
43 pub rec_len: u16,
44 pub name: Vec<u8>,
45 pub file_type: DirEntryType,
46}
47
48const HEADER_LEN: usize = 8;
49
50impl DirEntry {
51 pub fn parse(buf: &[u8]) -> Result<Self> {
56 if buf.len() < HEADER_LEN {
57 return Err(Ext4Error::TooShort {
58 structure: "DirEntry",
59 expected: HEADER_LEN,
60 found: buf.len(),
61 });
62 }
63
64 let inode = u32::from_le_bytes(buf[0..4].try_into().unwrap());
65 let rec_len = u16::from_le_bytes(buf[4..6].try_into().unwrap());
66 let name_len = buf[6] as usize;
67 let file_type = DirEntryType::from(buf[7]);
68
69 let needed = HEADER_LEN + name_len;
70 if buf.len() < needed {
71 return Err(Ext4Error::TooShort {
72 structure: "DirEntry name",
73 expected: needed,
74 found: buf.len(),
75 });
76 }
77
78 let name = buf[HEADER_LEN..HEADER_LEN + name_len].to_vec();
79
80 Ok(Self {
81 inode,
82 rec_len,
83 name,
84 file_type,
85 })
86 }
87
88 pub fn name_str(&self) -> String {
90 String::from_utf8_lossy(&self.name).into_owned()
91 }
92
93 pub fn is_deleted(&self) -> bool {
95 self.inode == 0
96 }
97}
98
99pub fn parse_dir_block(block: &[u8]) -> Vec<DirEntry> {
104 let mut entries = Vec::new();
105 let mut offset = 0usize;
106
107 while offset < block.len() {
108 let remaining = &block[offset..];
109 match DirEntry::parse(remaining) {
110 Ok(entry) => {
111 let rec_len = entry.rec_len as usize;
112 entries.push(entry);
113 if rec_len == 0 {
114 break; }
116 offset += rec_len;
117 }
118 Err(_) => break,
119 }
120 }
121
122 entries
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128
129 #[test]
130 fn parse_dir_entry() {
131 let mut buf = vec![0u8; 20];
132 buf[0] = 12; buf[4] = 20; buf[6] = 5; buf[7] = 1; buf[8..13].copy_from_slice(b"hello");
137 let entry = DirEntry::parse(&buf).unwrap();
138 assert_eq!(entry.inode, 12);
139 assert_eq!(entry.rec_len, 20);
140 assert_eq!(entry.name, b"hello");
141 assert_eq!(entry.file_type, DirEntryType::RegularFile);
142 }
143
144 #[test]
145 fn parse_dot_entries() {
146 let mut buf = vec![0u8; 12];
147 buf[0] = 2;
148 buf[4] = 12;
149 buf[6] = 1;
150 buf[7] = 2;
151 buf[8] = b'.';
152 let entry = DirEntry::parse(&buf).unwrap();
153 assert_eq!(entry.name, b".");
154 assert_eq!(entry.file_type, DirEntryType::Directory);
155 }
156
157 #[test]
158 fn skip_deleted_entry() {
159 let mut buf = vec![0u8; 12];
160 buf[0] = 0;
161 buf[4] = 12;
162 buf[6] = 3;
163 buf[7] = 1;
164 buf[8..11].copy_from_slice(b"foo");
165 let entry = DirEntry::parse(&buf).unwrap();
166 assert_eq!(entry.inode, 0);
167 }
168
169 #[test]
170 fn reject_too_short() {
171 let buf = vec![0u8; 4];
172 let err = DirEntry::parse(&buf).unwrap_err();
173 assert!(matches!(err, crate::error::Ext4Error::TooShort { .. }));
174 }
175
176 #[test]
177 fn is_deleted_flag() {
178 let mut buf = vec![0u8; 12];
179 buf[0] = 0;
180 buf[4] = 12;
181 buf[6] = 3;
182 buf[7] = 1;
183 buf[8..11].copy_from_slice(b"del");
184 let entry = DirEntry::parse(&buf).unwrap();
185 assert!(entry.is_deleted());
186 }
187
188 #[test]
189 fn name_str_utf8() {
190 let mut buf = vec![0u8; 13];
191 buf[0] = 5;
192 buf[4] = 13;
193 buf[6] = 5;
194 buf[7] = 1;
195 buf[8..13].copy_from_slice(b"world");
196 let entry = DirEntry::parse(&buf).unwrap();
197 assert_eq!(entry.name_str(), "world");
198 }
199
200 #[test]
201 fn parse_dir_block_multiple_entries() {
202 let mut block = vec![0u8; 24];
204 block[0] = 1;
206 block[4] = 12;
207 block[6] = 1;
208 block[7] = 2;
209 block[8] = b'.';
210 block[12] = 2;
212 block[16] = 12;
213 block[18] = 2;
214 block[19] = 2;
215 block[20] = b'.';
216 block[21] = b'.';
217 let entries = parse_dir_block(&block);
218 assert_eq!(entries.len(), 2);
219 assert_eq!(entries[0].name, b".");
220 assert_eq!(entries[1].name, b"..");
221 }
222
223 #[test]
224 fn dir_entry_type_from_u8() {
225 assert_eq!(DirEntryType::from(0), DirEntryType::Unknown);
226 assert_eq!(DirEntryType::from(1), DirEntryType::RegularFile);
227 assert_eq!(DirEntryType::from(2), DirEntryType::Directory);
228 assert_eq!(DirEntryType::from(3), DirEntryType::CharDevice);
229 assert_eq!(DirEntryType::from(4), DirEntryType::BlockDevice);
230 assert_eq!(DirEntryType::from(5), DirEntryType::Fifo);
231 assert_eq!(DirEntryType::from(6), DirEntryType::Socket);
232 assert_eq!(DirEntryType::from(7), DirEntryType::Symlink);
233 assert_eq!(DirEntryType::from(255), DirEntryType::Unknown);
234 }
235}