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 = safe_read::le_u32(buf, 0);
68 let rec_len = safe_read::le_u16(buf, 4);
69 let name_len = buf[6] as usize;
70 let file_type = DirEntryType::from(buf[7]);
71
72 let needed = HEADER_LEN + name_len;
73 if buf.len() < needed {
74 return Err(Ext4Error::TooShort {
75 structure: "DirEntry name",
76 expected: needed,
77 found: buf.len(),
78 });
79 }
80
81 let name = buf[HEADER_LEN..HEADER_LEN + name_len].to_vec();
82
83 Ok(Self {
84 inode,
85 rec_len,
86 name,
87 file_type,
88 })
89 }
90
91 pub fn name_str(&self) -> String {
93 String::from_utf8_lossy(&self.name).into_owned()
94 }
95
96 pub fn is_deleted(&self) -> bool {
98 self.inode == 0
99 }
100}
101
102pub fn parse_dir_block(block: &[u8]) -> Vec<DirEntry> {
107 let mut entries = Vec::new();
108 let mut offset = 0usize;
109
110 while offset < block.len() {
111 let remaining = &block[offset..];
112 match DirEntry::parse(remaining) {
113 Ok(entry) => {
114 let rec_len = entry.rec_len as usize;
115 entries.push(entry);
116 if rec_len == 0 {
117 break; }
119 offset += rec_len;
120 }
121 Err(_) => break,
122 }
123 }
124
125 entries
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131
132 #[test]
133 fn parse_dir_entry() {
134 let mut buf = vec![0u8; 20];
135 buf[0] = 12; buf[4] = 20; buf[6] = 5; buf[7] = 1; buf[8..13].copy_from_slice(b"hello");
140 let entry = DirEntry::parse(&buf).unwrap();
141 assert_eq!(entry.inode, 12);
142 assert_eq!(entry.rec_len, 20);
143 assert_eq!(entry.name, b"hello");
144 assert_eq!(entry.file_type, DirEntryType::RegularFile);
145 }
146
147 #[test]
148 fn parse_dot_entries() {
149 let mut buf = vec![0u8; 12];
150 buf[0] = 2;
151 buf[4] = 12;
152 buf[6] = 1;
153 buf[7] = 2;
154 buf[8] = b'.';
155 let entry = DirEntry::parse(&buf).unwrap();
156 assert_eq!(entry.name, b".");
157 assert_eq!(entry.file_type, DirEntryType::Directory);
158 }
159
160 #[test]
161 fn skip_deleted_entry() {
162 let mut buf = vec![0u8; 12];
163 buf[0] = 0;
164 buf[4] = 12;
165 buf[6] = 3;
166 buf[7] = 1;
167 buf[8..11].copy_from_slice(b"foo");
168 let entry = DirEntry::parse(&buf).unwrap();
169 assert_eq!(entry.inode, 0);
170 }
171
172 #[test]
173 fn reject_too_short() {
174 let buf = vec![0u8; 4];
175 let err = DirEntry::parse(&buf).unwrap_err();
176 assert!(matches!(err, crate::error::Ext4Error::TooShort { .. }));
177 }
178
179 #[test]
180 fn is_deleted_flag() {
181 let mut buf = vec![0u8; 12];
182 buf[0] = 0;
183 buf[4] = 12;
184 buf[6] = 3;
185 buf[7] = 1;
186 buf[8..11].copy_from_slice(b"del");
187 let entry = DirEntry::parse(&buf).unwrap();
188 assert!(entry.is_deleted());
189 }
190
191 #[test]
192 fn name_str_utf8() {
193 let mut buf = vec![0u8; 13];
194 buf[0] = 5;
195 buf[4] = 13;
196 buf[6] = 5;
197 buf[7] = 1;
198 buf[8..13].copy_from_slice(b"world");
199 let entry = DirEntry::parse(&buf).unwrap();
200 assert_eq!(entry.name_str(), "world");
201 }
202
203 #[test]
204 fn parse_dir_block_multiple_entries() {
205 let mut block = vec![0u8; 24];
207 block[0] = 1;
209 block[4] = 12;
210 block[6] = 1;
211 block[7] = 2;
212 block[8] = b'.';
213 block[12] = 2;
215 block[16] = 12;
216 block[18] = 2;
217 block[19] = 2;
218 block[20] = b'.';
219 block[21] = b'.';
220 let entries = parse_dir_block(&block);
221 assert_eq!(entries.len(), 2);
222 assert_eq!(entries[0].name, b".");
223 assert_eq!(entries[1].name, b"..");
224 }
225
226 #[test]
227 fn dir_entry_type_from_u8() {
228 assert_eq!(DirEntryType::from(0), DirEntryType::Unknown);
229 assert_eq!(DirEntryType::from(1), DirEntryType::RegularFile);
230 assert_eq!(DirEntryType::from(2), DirEntryType::Directory);
231 assert_eq!(DirEntryType::from(3), DirEntryType::CharDevice);
232 assert_eq!(DirEntryType::from(4), DirEntryType::BlockDevice);
233 assert_eq!(DirEntryType::from(5), DirEntryType::Fifo);
234 assert_eq!(DirEntryType::from(6), DirEntryType::Socket);
235 assert_eq!(DirEntryType::from(7), DirEntryType::Symlink);
236 assert_eq!(DirEntryType::from(255), DirEntryType::Unknown);
237 }
238}