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::num::NonZero;
22use core::str::Utf8Error;
23
24#[derive(Clone, Copy, Debug, Eq, PartialEq)]
26#[non_exhaustive]
27pub enum DirEntryNameError {
28 Empty,
30
31 TooLong,
33
34 ContainsNull,
36
37 ContainsSeparator,
39}
40
41impl Display for DirEntryNameError {
42 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
43 match self {
44 Self::Empty => write!(f, "direntry name is empty"),
45 Self::TooLong => {
46 write!(f, "directory entry name is longer than 255 bytes")
47 }
48 Self::ContainsNull => {
49 write!(f, "directory entry name contains a null byte")
50 }
51 Self::ContainsSeparator => {
52 write!(f, "directory entry name contains a path separator")
53 }
54 }
55 }
56}
57
58impl Error for DirEntryNameError {}
59
60#[derive(Clone, Copy, Eq, Ord, PartialOrd, Hash)]
65pub struct DirEntryName<'a>(pub(crate) &'a [u8]);
66
67impl<'a> DirEntryName<'a> {
68 pub const MAX_LEN: usize = 255;
70
71 #[inline]
73 pub fn as_str(&self) -> Result<&'a str, Utf8Error> {
74 core::str::from_utf8(self.0)
75 }
76
77 pub fn display(&self) -> BytesDisplay<'_> {
83 BytesDisplay(self.0)
84 }
85}
86
87impl<'a> AsRef<[u8]> for DirEntryName<'a> {
88 fn as_ref(&self) -> &'a [u8] {
89 self.0
90 }
91}
92
93impl Debug for DirEntryName<'_> {
94 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
95 format_bytes_debug(self.0, f)
96 }
97}
98
99impl<T> PartialEq<T> for DirEntryName<'_>
100where
101 T: AsRef<[u8]>,
102{
103 fn eq(&self, other: &T) -> bool {
104 self.0 == other.as_ref()
105 }
106}
107
108impl<'a> TryFrom<&'a [u8]> for DirEntryName<'a> {
109 type Error = DirEntryNameError;
110
111 fn try_from(bytes: &'a [u8]) -> Result<Self, DirEntryNameError> {
112 if bytes.is_empty() {
113 Err(DirEntryNameError::Empty)
114 } else if bytes.len() > Self::MAX_LEN {
115 Err(DirEntryNameError::TooLong)
116 } else if bytes.contains(&0) {
117 Err(DirEntryNameError::ContainsNull)
118 } else if bytes.contains(&Path::SEPARATOR) {
119 Err(DirEntryNameError::ContainsSeparator)
120 } else {
121 Ok(Self(bytes))
122 }
123 }
124}
125
126impl<'a, const N: usize> TryFrom<&'a [u8; N]> for DirEntryName<'a> {
127 type Error = DirEntryNameError;
128
129 fn try_from(bytes: &'a [u8; N]) -> Result<Self, DirEntryNameError> {
130 Self::try_from(bytes.as_slice())
131 }
132}
133
134impl<'a> TryFrom<&'a str> for DirEntryName<'a> {
135 type Error = DirEntryNameError;
136
137 fn try_from(s: &'a str) -> Result<Self, DirEntryNameError> {
138 Self::try_from(s.as_bytes())
139 }
140}
141
142#[derive(Clone, Eq, Ord, PartialOrd)]
143struct DirEntryNameBuf {
144 data: [u8; DirEntryName::MAX_LEN],
145 len: u8,
146}
147
148impl DirEntryNameBuf {
149 #[inline]
150 #[must_use]
151 fn as_bytes(&self) -> &[u8] {
152 &self.data[..usize::from(self.len)]
153 }
154
155 #[inline]
156 #[must_use]
157 fn as_dir_entry_name(&self) -> DirEntryName<'_> {
158 DirEntryName(self.as_bytes())
159 }
160}
161
162impl Debug for DirEntryNameBuf {
163 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
164 format_bytes_debug(self.as_bytes(), f)
165 }
166}
167
168impl PartialEq<Self> for DirEntryNameBuf {
171 fn eq(&self, other: &Self) -> bool {
172 self.as_bytes() == other.as_bytes()
173 }
174}
175
176impl Hash for DirEntryNameBuf {
179 fn hash<H>(&self, hasher: &mut H)
180 where
181 H: Hasher,
182 {
183 self.as_bytes().hash(hasher);
184 }
185}
186
187impl TryFrom<&[u8]> for DirEntryNameBuf {
188 type Error = DirEntryNameError;
189
190 fn try_from(bytes: &[u8]) -> Result<Self, DirEntryNameError> {
191 DirEntryName::try_from(bytes)?;
193
194 let mut name = Self {
195 data: [0; DirEntryName::MAX_LEN],
196 len: u8::try_from(bytes.len()).unwrap(),
198 };
199 name.data[..bytes.len()].copy_from_slice(bytes);
200 Ok(name)
201 }
202}
203
204#[derive(Clone)]
206pub struct DirEntry {
207 fs: Ext4,
208
209 pub(crate) inode: InodeIndex,
211
212 name: DirEntryNameBuf,
214
215 path: Rc<PathBuf>,
218
219 file_type: FileType,
221}
222
223impl DirEntry {
224 pub(crate) fn from_bytes(
238 fs: Ext4,
239 bytes: &[u8],
240 inode: InodeIndex,
241 path: Rc<PathBuf>,
242 ) -> Result<(Option<Self>, NonZero<usize>), Ext4Error> {
243 const NAME_OFFSET: usize = 8;
244
245 if bytes.len() < NAME_OFFSET {
248 return Err(
249 CorruptKind::DirEntryMissingHeader(inode, bytes.len()).into()
250 );
251 }
252
253 let points_to_inode = read_u32le(bytes, 0);
257
258 let rec_len = read_u16le(bytes, 4);
260 let rec_len = usize::from(rec_len);
261
262 if rec_len < NAME_OFFSET {
267 return Err(
268 CorruptKind::DirEntryRecordTooSmall(inode, rec_len).into()
269 );
270 }
271 let rec_len = NonZero::new(rec_len).unwrap();
273
274 let Some(points_to_inode) = InodeIndex::new(points_to_inode) else {
278 return Ok((None, rec_len));
279 };
280
281 let name_len = *bytes.get(6).unwrap();
284 let name_len_usize = usize::from(name_len);
285
286 let name_end: usize = NAME_OFFSET.checked_add(name_len_usize).unwrap();
290
291 let name_slice = bytes
293 .get(NAME_OFFSET..name_end)
294 .ok_or(CorruptKind::DirEntryNameTooLarge(inode, name_len))?;
295
296 let file_type = bytes[7];
303 let file_type = FileType::from_dir_entry(file_type).map_err(|_| {
304 CorruptKind::DirEntryInvalidFileType(inode, file_type)
305 })?;
306
307 let name = DirEntryNameBuf::try_from(name_slice)
308 .map_err(|e| CorruptKind::DirEntryInvalidName(inode, e))?;
309 let entry = Self {
310 fs,
311 inode: points_to_inode,
312 name,
313 path,
314 file_type,
315 };
316 Ok((Some(entry), rec_len))
317 }
318
319 #[must_use]
321 #[inline]
322 pub fn file_name(&self) -> DirEntryName<'_> {
323 self.name.as_dir_entry_name()
324 }
325
326 #[must_use]
331 pub fn path(&self) -> PathBuf {
332 self.path.join(self.name.as_bytes())
333 }
334
335 pub fn file_type(&self) -> Result<FileType, Ext4Error> {
337 Ok(self.file_type)
342 }
343
344 pub fn metadata(&self) -> Result<Metadata, Ext4Error> {
349 let inode = Inode::read(&self.fs, self.inode)?;
350 Ok(inode.metadata)
351 }
352}
353
354impl Debug for DirEntry {
355 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
356 f.debug_tuple("DirEntry").field(&self.path()).finish()
357 }
358}
359
360#[cfg(test)]
361mod tests {
362 use super::*;
363 use std::hash::DefaultHasher;
364
365 #[test]
366 fn test_dir_entry_debug() {
367 let src = "abc😁\n".as_bytes();
368 let expected = r#""abc😁\n""#; assert_eq!(format!("{:?}", DirEntryName(src)), expected);
370
371 let mut src_vec = src.to_vec();
372 src_vec.resize(255, 0);
373 assert_eq!(
374 format!(
375 "{:?}",
376 DirEntryNameBuf {
377 data: src_vec.try_into().unwrap(),
378 len: src.len().try_into().unwrap(),
379 }
380 ),
381 expected
382 );
383 }
384
385 #[test]
386 fn test_dir_entry_display() {
387 let name = DirEntryName([0xc3, 0x28].as_slice());
388 assert_eq!(format!("{}", name.display()), "�(");
389 }
390
391 #[test]
392 fn test_dir_entry_construction() {
393 let expected_name = DirEntryName(b"abc");
394 let mut v = b"abc".to_vec();
395 v.resize(255, 0);
396 let expected_name_buf = DirEntryNameBuf {
397 data: v.try_into().unwrap(),
398 len: 3,
399 };
400
401 let src: &[u8] = b"abc";
403 assert_eq!(DirEntryName::try_from(src).unwrap(), expected_name);
404 assert_eq!(DirEntryNameBuf::try_from(src).unwrap(), expected_name_buf);
405
406 let src: &str = "abc";
408 assert_eq!(DirEntryName::try_from(src).unwrap(), expected_name);
409
410 let src: &[u8; 3] = b"abc";
412 assert_eq!(DirEntryName::try_from(src).unwrap(), expected_name);
413
414 let src: &[u8] = b"";
416 assert_eq!(DirEntryName::try_from(src), Err(DirEntryNameError::Empty));
417 assert_eq!(
418 DirEntryNameBuf::try_from(src),
419 Err(DirEntryNameError::Empty)
420 );
421
422 let src: &[u8] = [1; 256].as_slice();
424 assert_eq!(
425 DirEntryName::try_from(src),
426 Err(DirEntryNameError::TooLong)
427 );
428 assert_eq!(
429 DirEntryNameBuf::try_from(src),
430 Err(DirEntryNameError::TooLong)
431 );
432
433 let src: &[u8] = b"\0".as_slice();
435 assert_eq!(
436 DirEntryName::try_from(src),
437 Err(DirEntryNameError::ContainsNull)
438 );
439 assert_eq!(
440 DirEntryNameBuf::try_from(src),
441 Err(DirEntryNameError::ContainsNull)
442 );
443
444 let src: &[u8] = b"/".as_slice();
446 assert_eq!(
447 DirEntryName::try_from(src),
448 Err(DirEntryNameError::ContainsSeparator)
449 );
450 assert_eq!(
451 DirEntryNameBuf::try_from(src),
452 Err(DirEntryNameError::ContainsSeparator)
453 );
454 }
455
456 #[test]
457 fn test_dir_entry_name_buf_hash() {
458 fn get_hash<T: Hash>(v: T) -> u64 {
459 let mut s = DefaultHasher::new();
460 v.hash(&mut s);
461 s.finish()
462 }
463
464 let name = DirEntryNameBuf::try_from(b"abc".as_slice()).unwrap();
465 assert_eq!(get_hash(name), get_hash(b"abc"));
466 }
467
468 #[cfg(feature = "std")]
469 #[test]
470 fn test_dir_entry_from_bytes() {
471 let fs = crate::test_util::load_test_disk1();
472
473 let inode1 = InodeIndex::new(1).unwrap();
474 let inode2 = InodeIndex::new(2).unwrap();
475 let path = Rc::new(PathBuf::new("path"));
476
477 let mut bytes = Vec::new();
479 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);
485 let (entry, len) =
486 DirEntry::from_bytes(fs.clone(), &bytes, inode1, path.clone())
487 .unwrap();
488 let entry = entry.unwrap();
489 assert_eq!(len.get(), 72);
490 assert_eq!(entry.inode, inode2);
491 assert_eq!(
492 entry.name,
493 DirEntryNameBuf::try_from("abc".as_bytes()).unwrap()
494 );
495 assert_eq!(entry.path, path);
496 assert_eq!(entry.file_type, FileType::Regular);
497 assert_eq!(entry.file_name(), "abc");
498 assert_eq!(entry.path(), "path/abc");
499
500 let mut bytes = Vec::new();
502 bytes.extend(0u32.to_le_bytes()); bytes.extend(72u16.to_le_bytes()); bytes.resize(72, 0u8);
505 let (entry, len) =
506 DirEntry::from_bytes(fs.clone(), &bytes, inode1, path.clone())
507 .unwrap();
508 assert!(entry.is_none());
509 assert_eq!(len.get(), 72);
510
511 assert_eq!(
513 DirEntry::from_bytes(fs.clone(), &[], inode1, path.clone())
514 .unwrap_err(),
515 CorruptKind::DirEntryMissingHeader(inode1, 0)
516 );
517
518 let mut bytes = Vec::new();
520 bytes.extend(2u32.to_le_bytes()); bytes.extend(7u16.to_le_bytes()); bytes.resize(72, 0u8);
523 assert_eq!(
524 DirEntry::from_bytes(fs.clone(), &bytes, inode1, path.clone())
525 .unwrap_err(),
526 CorruptKind::DirEntryRecordTooSmall(inode1, 7)
527 );
528
529 let mut bytes = Vec::new();
531 bytes.extend(2u32.to_le_bytes()); bytes.extend(72u16.to_le_bytes()); bytes.push(3u8); bytes.push(123u8); bytes.extend("abc".bytes()); bytes.resize(72, 0u8);
537 assert_eq!(
538 DirEntry::from_bytes(fs.clone(), &bytes, inode1, path.clone())
539 .unwrap_err(),
540 CorruptKind::DirEntryInvalidFileType(inode1, 123),
541 );
542
543 let mut bytes = Vec::new();
545 bytes.extend(2u32.to_le_bytes()); bytes.extend(72u16.to_le_bytes()); bytes.push(3u8); bytes.push(1u8); bytes.extend("a".bytes()); assert_eq!(
551 DirEntry::from_bytes(fs.clone(), &bytes, inode1, path.clone())
552 .unwrap_err(),
553 CorruptKind::DirEntryNameTooLarge(inode1, 3),
554 );
555
556 let mut bytes = Vec::new();
558 bytes.extend(2u32.to_le_bytes()); bytes.extend(72u16.to_le_bytes()); bytes.push(3u8); bytes.push(1u8); bytes.extend("ab/".bytes()); bytes.resize(72, 0u8);
564 assert_eq!(
565 DirEntry::from_bytes(fs.clone(), &bytes, inode1, path).unwrap_err(),
566 CorruptKind::DirEntryInvalidName(
567 inode1,
568 DirEntryNameError::ContainsSeparator
569 ),
570 );
571 }
572
573 #[test]
574 fn test_dir_entry_name_as_ref() {
575 let name = DirEntryName::try_from(b"abc".as_slice()).unwrap();
576 let bytes: &[u8] = name.as_ref();
577 assert_eq!(bytes, b"abc");
578 }
579
580 #[test]
581 fn test_dir_entry_name_partial_eq() {
582 let name = DirEntryName::try_from(b"abc".as_slice()).unwrap();
583 assert_eq!(name, name);
584
585 let v: &str = "abc";
586 assert_eq!(name, v);
587
588 let v: &[u8] = b"abc";
589 assert_eq!(name, v);
590
591 let v: &[u8; 3] = b"abc";
592 assert_eq!(name, v);
593 }
594
595 #[test]
596 fn test_dir_entry_name_buf_as_dir_entry_name() {
597 let name = DirEntryNameBuf::try_from(b"abc".as_slice()).unwrap();
598 let r: DirEntryName<'_> = name.as_dir_entry_name();
599 assert_eq!(r, "abc");
600 }
601
602 #[test]
603 fn test_dir_entry_name_as_str() {
604 let name = DirEntryName::try_from(b"abc".as_slice()).unwrap();
605 assert_eq!(name.as_str().unwrap(), "abc");
606
607 let name = DirEntryName([0xc3, 0x28].as_slice());
608 assert!(name.as_str().is_err());
609 }
610}