1use crate::crypto::PerFileDecryptor;
5use crate::superblock::BLOCK_SIZE;
6use anyhow::{Context, Error, anyhow, ensure};
7use enumn::N;
8use fscrypt::proxy_filename::ProxyFilename;
9use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Ref, Unaligned};
10
11#[derive(Copy, Clone, Debug, Eq, PartialEq, N)]
12#[repr(u8)]
13pub enum FileType {
14 Unknown = 0,
15 RegularFile = 1,
16 Directory = 2,
17 CharDevice = 3,
18 BlockDevice = 4,
19 Fifo = 5,
20 Socket = 6,
21 Symlink = 7,
22}
23
24#[repr(C, packed)]
25#[derive(Copy, Clone, Debug, FromBytes, IntoBytes, Immutable, KnownLayout, Unaligned)]
26pub struct RawDirEntry {
27 pub hash_code: u32,
28 pub ino: u32,
29 pub name_len: u16,
30 pub file_type: u8,
31}
32
33#[derive(Clone, Debug)]
34pub struct InlineDentry {
35 pub dentry_bitmap: Box<[u8]>,
36 pub dentry: Box<[RawDirEntry]>,
37 pub filenames: Box<[u8]>,
38}
39
40pub const NAME_LEN: usize = 8;
41pub const NUM_DENTRY_IN_BLOCK: usize = 214;
42pub const SIZE_OF_DENTRY_BITMAP: usize = (NUM_DENTRY_IN_BLOCK + 7) / 8;
44pub const SIZE_OF_DENTRY_RESERVED: usize = BLOCK_SIZE
46 - ((std::mem::size_of::<RawDirEntry>() + NAME_LEN) * NUM_DENTRY_IN_BLOCK
47 + SIZE_OF_DENTRY_BITMAP);
48
49#[repr(C, packed)]
50#[derive(Copy, Clone, Debug, FromBytes, Immutable, KnownLayout, IntoBytes, Unaligned)]
51pub struct DentryBlock {
52 dentry_bitmap: [u8; SIZE_OF_DENTRY_BITMAP],
53 _reserved: [u8; SIZE_OF_DENTRY_RESERVED],
54 dentry: [RawDirEntry; NUM_DENTRY_IN_BLOCK],
55 filenames: [u8; NUM_DENTRY_IN_BLOCK * NAME_LEN],
56}
57
58#[derive(Debug)]
59pub struct DirEntry {
60 pub hash_code: u32,
61 pub ino: u32,
62 pub filename: String,
63 pub file_type: FileType,
64 pub raw_filename: Vec<u8>,
65}
66
67fn get_dir_entries(
70 ino: u32,
71 dentry_bitmap: &[u8],
72 dentry: &[RawDirEntry],
73 filenames: &[u8],
74 is_encrypted: bool,
75 is_casefolded: bool,
76 decryptor: &Option<PerFileDecryptor>,
77) -> Result<Vec<DirEntry>, Error> {
78 debug_assert!(dentry_bitmap.len() * 8 >= dentry.len(), "bitmap too small");
79 debug_assert_eq!(dentry.len() * 8, filenames.len(), "dentry len different to filenames len");
80 let mut out = Vec::new();
81 let mut i = 0;
82 while i < dentry.len() {
83 let entry = dentry[i];
84 if (dentry_bitmap[i / 8] >> (i % 8)) & 0x1 == 0 || dentry[i].ino == 0 {
86 i += 1;
87 continue;
88 }
89 let name_len = dentry[i].name_len as usize;
90 if name_len == 0 {
91 i += 1;
92 continue;
93 }
94 ensure!(i * NAME_LEN + name_len <= filenames.len(), "Filename doesn't fit in buffer");
96 let raw_filename = filenames[i * NAME_LEN..i * NAME_LEN + name_len].to_vec();
97 if raw_filename == b"." || raw_filename == b".." {
99 i += 1;
100 continue;
101 }
102 let filename = if is_encrypted {
104 if let Some(decryptor) = decryptor {
105 let mut filename = raw_filename.clone();
106 decryptor.decrypt_filename_data(ino, &mut filename);
107 while filename.last() == Some(&0) {
108 filename.pop();
109 }
110 let hash_code = if is_casefolded {
112 fscrypt::direntry::casefold_encrypt_hash_filename(
113 filename.as_slice(),
114 &decryptor.dirhash_key(),
115 )
116 } else {
117 fscrypt::direntry::tea_hash_filename(raw_filename.as_slice())
118 };
119
120 let target = dentry[i].hash_code;
121 ensure!(target == hash_code, "hash_code doesn't match expectation");
122 let filename_len = filename.len();
123 String::from_utf8(filename)
124 .unwrap_or_else(|_| format!("BAD_ENCRYPTED_FILENAME_len_{filename_len}"))
125 } else {
126 ProxyFilename::new_with_hash_code(entry.hash_code as u64, &raw_filename).into()
128 }
129 } else {
130 str::from_utf8(&raw_filename).context("Bad UTF8 filename")?.to_string()
131 };
132 let file_type = FileType::n(dentry[i].file_type).ok_or_else(|| anyhow!("Bad file type"))?;
133 out.push(DirEntry {
134 hash_code: entry.hash_code,
135 ino: entry.ino,
136 filename,
137 file_type,
138 raw_filename,
139 });
140 i += (name_len + NAME_LEN - 1) / NAME_LEN;
141 }
142 Ok(out)
143}
144
145impl DentryBlock {
146 pub fn get_entries(
147 &self,
148 ino: u32,
149 is_encrypted: bool,
150 is_casefolded: bool,
151 decryptor: &Option<PerFileDecryptor>,
152 ) -> Result<Vec<DirEntry>, Error> {
153 get_dir_entries(
154 ino,
155 &self.dentry_bitmap,
156 &self.dentry,
157 &self.filenames,
158 is_encrypted,
159 is_casefolded,
160 decryptor,
161 )
162 }
163}
164
165impl crate::inode::Inode {
166 pub fn get_inline_dir_entries(
167 &self,
168 is_encrypted: bool,
169 is_casefolded: bool,
170 decryptor: &Option<PerFileDecryptor>,
171 ) -> Result<Option<Vec<DirEntry>>, Error> {
172 if let Some(inline_dentry) = &self.inline_dentry {
173 Ok(Some(get_dir_entries(
174 self.footer.ino,
175 &inline_dentry.dentry_bitmap,
176 &inline_dentry.dentry,
177 &inline_dentry.filenames,
178 is_encrypted,
179 is_casefolded,
180 decryptor,
181 )?))
182 } else {
183 Ok(None)
184 }
185 }
186}
187
188impl InlineDentry {
189 pub fn try_from_bytes(rest: &[u8]) -> Result<Self, Error> {
190 ensure!(rest.len() % 4 == 0, "Bad alignment in inode inline_dentry");
191 let rest = &rest[4..];
193 let dentry_count =
211 8 * rest.len() / (8 * (std::mem::size_of::<RawDirEntry>() + NAME_LEN) + 1);
212 let (dentry_bitmap, rest): (Ref<_, [u8]>, _) =
213 Ref::from_prefix_with_elems(rest, (dentry_count + 7) / 8).unwrap();
214 let (rest, filenames): (_, Ref<_, [u8]>) =
215 Ref::from_suffix_with_elems(rest, dentry_count * NAME_LEN).unwrap();
216 let (_, dentry): (_, Ref<_, [RawDirEntry]>) =
218 Ref::from_suffix_with_elems(rest, dentry_count).unwrap();
219 Ok(InlineDentry {
220 dentry_bitmap: (*dentry_bitmap).into(),
221 dentry: (*dentry).into(),
222 filenames: (*filenames).into(),
223 })
224 }
225}