1use crate::Ext4;
10use crate::error::{CorruptKind, Ext4Error};
11use crate::file_type::FileType;
12use crate::format::{BytesDisplay, format_bytes_debug};
13use crate::inode::{Inode, InodeIndex};
14use crate::metadata::Metadata;
15use crate::path::{Path, PathBuf};
16use crate::util::{read_u16le, read_u32le};
17use alloc::rc::Rc;
18use core::error::Error;
19use core::fmt::{self, Debug, Display, Formatter};
20use core::hash::{Hash, Hasher};
21use core::str::Utf8Error;
22
23#[derive(Clone, Copy, Debug, Eq, PartialEq)]
25#[non_exhaustive]
26pub enum DirEntryNameError {
27 Empty,
29
30 TooLong,
32
33 ContainsNull,
35
36 ContainsSeparator,
38}
39
40impl Display for DirEntryNameError {
41 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
42 match self {
43 Self::Empty => write!(f, "direntry name is empty"),
44 Self::TooLong => {
45 write!(f, "directory entry name is longer than 255 bytes")
46 }
47 Self::ContainsNull => {
48 write!(f, "directory entry name contains a null byte")
49 }
50 Self::ContainsSeparator => {
51 write!(f, "directory entry name contains a path separator")
52 }
53 }
54 }
55}
56
57impl Error for DirEntryNameError {}
58
59#[derive(Clone, Copy, Eq, Ord, PartialOrd, Hash)]
64pub struct DirEntryName<'a>(pub(crate) &'a [u8]);
65
66impl<'a> DirEntryName<'a> {
67 pub const MAX_LEN: usize = 255;
69
70 #[inline]
72 pub fn as_str(&self) -> Result<&'a str, Utf8Error> {
73 core::str::from_utf8(self.0)
74 }
75
76 pub fn display(&self) -> BytesDisplay {
82 BytesDisplay(self.0)
83 }
84}
85
86impl<'a> AsRef<[u8]> for DirEntryName<'a> {
87 fn as_ref(&self) -> &'a [u8] {
88 self.0
89 }
90}
91
92impl Debug for DirEntryName<'_> {
93 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
94 format_bytes_debug(self.0, f)
95 }
96}
97
98impl<T> PartialEq<T> for DirEntryName<'_>
99where
100 T: AsRef<[u8]>,
101{
102 fn eq(&self, other: &T) -> bool {
103 self.0 == other.as_ref()
104 }
105}
106
107impl<'a> TryFrom<&'a [u8]> for DirEntryName<'a> {
108 type Error = DirEntryNameError;
109
110 fn try_from(bytes: &'a [u8]) -> Result<Self, DirEntryNameError> {
111 if bytes.is_empty() {
112 Err(DirEntryNameError::Empty)
113 } else if bytes.len() > Self::MAX_LEN {
114 Err(DirEntryNameError::TooLong)
115 } else if bytes.contains(&0) {
116 Err(DirEntryNameError::ContainsNull)
117 } else if bytes.contains(&Path::SEPARATOR) {
118 Err(DirEntryNameError::ContainsSeparator)
119 } else {
120 Ok(Self(bytes))
121 }
122 }
123}
124
125impl<'a, const N: usize> TryFrom<&'a [u8; N]> for DirEntryName<'a> {
126 type Error = DirEntryNameError;
127
128 fn try_from(bytes: &'a [u8; N]) -> Result<Self, DirEntryNameError> {
129 Self::try_from(bytes.as_slice())
130 }
131}
132
133impl<'a> TryFrom<&'a str> for DirEntryName<'a> {
134 type Error = DirEntryNameError;
135
136 fn try_from(s: &'a str) -> Result<Self, DirEntryNameError> {
137 Self::try_from(s.as_bytes())
138 }
139}
140
141#[derive(Clone, Eq, Ord, PartialOrd)]
142struct DirEntryNameBuf {
143 data: [u8; DirEntryName::MAX_LEN],
144 len: u8,
145}
146
147impl DirEntryNameBuf {
148 #[inline]
149 #[must_use]
150 fn as_bytes(&self) -> &[u8] {
151 &self.data[..usize::from(self.len)]
152 }
153
154 #[inline]
155 #[must_use]
156 fn as_dir_entry_name(&self) -> DirEntryName<'_> {
157 DirEntryName(self.as_bytes())
158 }
159}
160
161impl Debug for DirEntryNameBuf {
162 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
163 format_bytes_debug(self.as_bytes(), f)
164 }
165}
166
167impl PartialEq<Self> for DirEntryNameBuf {
170 fn eq(&self, other: &Self) -> bool {
171 self.as_bytes() == other.as_bytes()
172 }
173}
174
175impl Hash for DirEntryNameBuf {
178 fn hash<H>(&self, hasher: &mut H)
179 where
180 H: Hasher,
181 {
182 self.as_bytes().hash(hasher);
183 }
184}
185
186impl TryFrom<&[u8]> for DirEntryNameBuf {
187 type Error = DirEntryNameError;
188
189 fn try_from(bytes: &[u8]) -> Result<Self, DirEntryNameError> {
190 DirEntryName::try_from(bytes)?;
192
193 let mut name = Self {
194 data: [0; DirEntryName::MAX_LEN],
195 len: u8::try_from(bytes.len()).unwrap(),
197 };
198 name.data[..bytes.len()].copy_from_slice(bytes);
199 Ok(name)
200 }
201}
202
203#[derive(Clone, Debug)]
205pub struct DirEntry {
206 fs: Ext4,
207
208 pub(crate) inode: InodeIndex,
210
211 name: DirEntryNameBuf,
213
214 path: Rc<PathBuf>,
217
218 file_type: FileType,
220}
221
222impl DirEntry {
223 pub(crate) fn from_bytes(
233 fs: Ext4,
234 bytes: &[u8],
235 inode: InodeIndex,
236 path: Rc<PathBuf>,
237 ) -> Result<(Option<Self>, usize), Ext4Error> {
238 const NAME_OFFSET: usize = 8;
239
240 let err = || CorruptKind::DirEntry(inode).into();
241
242 if bytes.len() < NAME_OFFSET {
245 return Err(err());
246 }
247
248 let points_to_inode = read_u32le(bytes, 0);
252
253 let rec_len = read_u16le(bytes, 4);
255 let rec_len = usize::from(rec_len);
256
257 if rec_len < NAME_OFFSET {
262 return Err(err());
263 }
264
265 let Some(points_to_inode) = InodeIndex::new(points_to_inode) else {
269 return Ok((None, rec_len));
270 };
271
272 let name_len = *bytes.get(6).unwrap();
275 let name_len_usize = usize::from(name_len);
276
277 let name_end: usize = NAME_OFFSET.checked_add(name_len_usize).unwrap();
281
282 let name_slice = bytes.get(NAME_OFFSET..name_end).ok_or(err())?;
284
285 let file_type = bytes[7];
292 let file_type =
293 FileType::from_dir_entry(file_type).map_err(|_| err())?;
294
295 let name = DirEntryNameBuf::try_from(name_slice).map_err(|_| err())?;
296 let entry = Self {
297 fs,
298 inode: points_to_inode,
299 name,
300 path,
301 file_type,
302 };
303 Ok((Some(entry), rec_len))
304 }
305
306 #[must_use]
308 #[inline]
309 pub fn file_name(&self) -> DirEntryName<'_> {
310 self.name.as_dir_entry_name()
311 }
312
313 #[must_use]
318 pub fn path(&self) -> PathBuf {
319 self.path.join(self.name.as_bytes())
320 }
321
322 pub fn file_type(&self) -> Result<FileType, Ext4Error> {
324 Ok(self.file_type)
329 }
330
331 pub fn metadata(&self) -> Result<Metadata, Ext4Error> {
336 let inode = Inode::read(&self.fs, self.inode)?;
337 Ok(inode.metadata)
338 }
339}
340
341#[cfg(test)]
342mod tests {
343 use super::*;
344 use std::hash::DefaultHasher;
345
346 #[test]
347 fn test_dir_entry_debug() {
348 let src = "abc😁\n".as_bytes();
349 let expected = "abc😁\\n"; assert_eq!(format!("{:?}", DirEntryName(src)), expected);
351
352 let mut src_vec = src.to_vec();
353 src_vec.resize(255, 0);
354 assert_eq!(
355 format!(
356 "{:?}",
357 DirEntryNameBuf {
358 data: src_vec.try_into().unwrap(),
359 len: src.len().try_into().unwrap(),
360 }
361 ),
362 expected
363 );
364 }
365
366 #[test]
367 fn test_dir_entry_display() {
368 let name = DirEntryName([0xc3, 0x28].as_slice());
369 assert_eq!(format!("{}", name.display()), "�(");
370 }
371
372 #[test]
373 fn test_dir_entry_construction() {
374 let expected_name = DirEntryName(b"abc");
375 let mut v = b"abc".to_vec();
376 v.resize(255, 0);
377 let expected_name_buf = DirEntryNameBuf {
378 data: v.try_into().unwrap(),
379 len: 3,
380 };
381
382 let src: &[u8] = b"abc";
384 assert_eq!(DirEntryName::try_from(src).unwrap(), expected_name);
385 assert_eq!(DirEntryNameBuf::try_from(src).unwrap(), expected_name_buf);
386
387 let src: &str = "abc";
389 assert_eq!(DirEntryName::try_from(src).unwrap(), expected_name);
390
391 let src: &[u8; 3] = b"abc";
393 assert_eq!(DirEntryName::try_from(src).unwrap(), expected_name);
394
395 let src: &[u8] = b"";
397 assert_eq!(DirEntryName::try_from(src), Err(DirEntryNameError::Empty));
398 assert_eq!(
399 DirEntryNameBuf::try_from(src),
400 Err(DirEntryNameError::Empty)
401 );
402
403 let src: &[u8] = [1; 256].as_slice();
405 assert_eq!(
406 DirEntryName::try_from(src),
407 Err(DirEntryNameError::TooLong)
408 );
409 assert_eq!(
410 DirEntryNameBuf::try_from(src),
411 Err(DirEntryNameError::TooLong)
412 );
413
414 let src: &[u8] = b"\0".as_slice();
416 assert_eq!(
417 DirEntryName::try_from(src),
418 Err(DirEntryNameError::ContainsNull)
419 );
420 assert_eq!(
421 DirEntryNameBuf::try_from(src),
422 Err(DirEntryNameError::ContainsNull)
423 );
424
425 let src: &[u8] = b"/".as_slice();
427 assert_eq!(
428 DirEntryName::try_from(src),
429 Err(DirEntryNameError::ContainsSeparator)
430 );
431 assert_eq!(
432 DirEntryNameBuf::try_from(src),
433 Err(DirEntryNameError::ContainsSeparator)
434 );
435 }
436
437 #[test]
438 fn test_dir_entry_name_buf_hash() {
439 fn get_hash<T: Hash>(v: T) -> u64 {
440 let mut s = DefaultHasher::new();
441 v.hash(&mut s);
442 s.finish()
443 }
444
445 let name = DirEntryNameBuf::try_from(b"abc".as_slice()).unwrap();
446 assert_eq!(get_hash(name), get_hash(b"abc"));
447 }
448
449 #[cfg(feature = "std")]
450 #[test]
451 fn test_dir_entry_from_bytes() {
452 let fs = crate::test_util::load_test_disk1();
453
454 let inode1 = InodeIndex::new(1).unwrap();
455 let inode2 = InodeIndex::new(2).unwrap();
456 let path = Rc::new(PathBuf::new("path"));
457
458 let mut bytes = Vec::new();
460 bytes.extend(2u32.to_le_bytes()); bytes.extend(72u16.to_le_bytes()); bytes.push(3u8); bytes.push(1u8); bytes.extend("abc".bytes()); bytes.resize(72, 0u8);
466 let (entry, len) =
467 DirEntry::from_bytes(fs.clone(), &bytes, inode1, path.clone())
468 .unwrap();
469 let entry = entry.unwrap();
470 assert_eq!(len, 72);
471 assert_eq!(entry.inode, inode2);
472 assert_eq!(
473 entry.name,
474 DirEntryNameBuf::try_from("abc".as_bytes()).unwrap()
475 );
476 assert_eq!(entry.path, path);
477 assert_eq!(entry.file_type, FileType::Regular);
478 assert_eq!(entry.file_name(), "abc");
479 assert_eq!(entry.path(), "path/abc");
480
481 let mut bytes = Vec::new();
483 bytes.extend(0u32.to_le_bytes()); bytes.extend(72u16.to_le_bytes()); bytes.resize(72, 0u8);
486 let (entry, len) =
487 DirEntry::from_bytes(fs.clone(), &bytes, inode1, path.clone())
488 .unwrap();
489 assert!(entry.is_none());
490 assert_eq!(len, 72);
491
492 assert_eq!(
494 DirEntry::from_bytes(fs.clone(), &[], inode1, path.clone())
495 .unwrap_err(),
496 CorruptKind::DirEntry(inode1)
497 );
498
499 let mut bytes = Vec::new();
501 bytes.extend(2u32.to_le_bytes()); bytes.extend(72u16.to_le_bytes()); bytes.push(3u8); bytes.push(8u8); bytes.extend("a".bytes()); assert!(
507 DirEntry::from_bytes(fs.clone(), &bytes, inode1, path.clone())
508 .is_err()
509 );
510
511 let mut bytes = Vec::new();
513 bytes.extend(2u32.to_le_bytes()); bytes.extend(72u16.to_le_bytes()); bytes.push(3u8); bytes.push(8u8); bytes.extend("ab/".bytes()); bytes.resize(72, 0u8);
519 assert!(
520 DirEntry::from_bytes(fs.clone(), &bytes, inode1, path).is_err()
521 );
522 }
523
524 #[test]
525 fn test_dir_entry_name_as_ref() {
526 let name = DirEntryName::try_from(b"abc".as_slice()).unwrap();
527 let bytes: &[u8] = name.as_ref();
528 assert_eq!(bytes, b"abc");
529 }
530
531 #[test]
532 fn test_dir_entry_name_partial_eq() {
533 let name = DirEntryName::try_from(b"abc".as_slice()).unwrap();
534 assert_eq!(name, name);
535
536 let v: &str = "abc";
537 assert_eq!(name, v);
538
539 let v: &[u8] = b"abc";
540 assert_eq!(name, v);
541
542 let v: &[u8; 3] = b"abc";
543 assert_eq!(name, v);
544 }
545
546 #[test]
547 fn test_dir_entry_name_buf_as_dir_entry_name() {
548 let name = DirEntryNameBuf::try_from(b"abc".as_slice()).unwrap();
549 let r: DirEntryName<'_> = name.as_dir_entry_name();
550 assert_eq!(r, "abc");
551 }
552
553 #[test]
554 fn test_dir_entry_name_as_str() {
555 let name = DirEntryName::try_from(b"abc".as_slice()).unwrap();
556 assert_eq!(name.as_str().unwrap(), "abc");
557
558 let name = DirEntryName([0xc3, 0x28].as_slice());
559 assert!(name.as_str().is_err());
560 }
561}