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 filename_str = if is_casefolded {
111 String::from_utf8(filename)
113 .context("Bad UTF-8 in encrypted casefolded filename")?
114 } else {
115 let filename_len = filename.len();
116 String::from_utf8(filename)
117 .unwrap_or_else(|_| format!("BAD_ENCRYPTED_FILENAME_len_{filename_len}"))
118 };
119
120 let hash_code = if is_casefolded {
122 fscrypt::direntry::casefold_encrypt_hash_filename(
123 filename_str.as_str().into(),
124 &decryptor.dirhash_key(),
125 )
126 } else {
127 fscrypt::direntry::tea_hash_filename(raw_filename.iter().copied())
128 };
129
130 let target = dentry[i].hash_code;
131 ensure!(target == hash_code, "hash_code doesn't match expectation");
132 filename_str
133 } else {
134 ProxyFilename::new_with_hash_code(entry.hash_code as u64, &raw_filename).into()
136 }
137 } else {
138 str::from_utf8(&raw_filename).context("Bad UTF8 filename")?.to_string()
139 };
140 let file_type = FileType::n(dentry[i].file_type).ok_or_else(|| anyhow!("Bad file type"))?;
141 out.push(DirEntry {
142 hash_code: entry.hash_code,
143 ino: entry.ino,
144 filename,
145 file_type,
146 raw_filename,
147 });
148 i += (name_len + NAME_LEN - 1) / NAME_LEN;
149 }
150 Ok(out)
151}
152
153impl DentryBlock {
154 pub fn get_entries(
155 &self,
156 ino: u32,
157 is_encrypted: bool,
158 is_casefolded: bool,
159 decryptor: &Option<PerFileDecryptor>,
160 ) -> Result<Vec<DirEntry>, Error> {
161 get_dir_entries(
162 ino,
163 &self.dentry_bitmap,
164 &self.dentry,
165 &self.filenames,
166 is_encrypted,
167 is_casefolded,
168 decryptor,
169 )
170 }
171}
172
173impl crate::inode::Inode {
174 pub fn get_inline_dir_entries(
175 &self,
176 is_encrypted: bool,
177 is_casefolded: bool,
178 decryptor: &Option<PerFileDecryptor>,
179 ) -> Result<Option<Vec<DirEntry>>, Error> {
180 if let Some(inline_dentry) = &self.inline_dentry {
181 Ok(Some(get_dir_entries(
182 self.footer.ino,
183 &inline_dentry.dentry_bitmap,
184 &inline_dentry.dentry,
185 &inline_dentry.filenames,
186 is_encrypted,
187 is_casefolded,
188 decryptor,
189 )?))
190 } else {
191 Ok(None)
192 }
193 }
194}
195
196impl InlineDentry {
197 pub fn try_from_bytes(rest: &[u8]) -> Result<Self, Error> {
198 ensure!(rest.len() % 4 == 0, "Bad alignment in inode inline_dentry");
199 let rest = &rest[4..];
201 let dentry_count =
219 8 * rest.len() / (8 * (std::mem::size_of::<RawDirEntry>() + NAME_LEN) + 1);
220 let (dentry_bitmap, rest): (Ref<_, [u8]>, _) =
221 Ref::from_prefix_with_elems(rest, (dentry_count + 7) / 8).unwrap();
222 let (rest, filenames): (_, Ref<_, [u8]>) =
223 Ref::from_suffix_with_elems(rest, dentry_count * NAME_LEN).unwrap();
224 let (_, dentry): (_, Ref<_, [RawDirEntry]>) =
226 Ref::from_suffix_with_elems(rest, dentry_count).unwrap();
227 Ok(InlineDentry {
228 dentry_bitmap: (*dentry_bitmap).into(),
229 dentry: (*dentry).into(),
230 filenames: (*filenames).into(),
231 })
232 }
233}