1use crate::crypto::PerFileDecryptor;
5use crate::superblock::BLOCK_SIZE;
6use anyhow::{anyhow, ensure, Context, Error};
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 hash_code = fscrypt::direntry::tea_hash_filename(&raw_filename[..name_len]);
106 let mut filename = raw_filename.clone();
107 decryptor.decrypt_filename_data(ino, &mut filename);
108 while filename.last() == Some(&0) {
109 filename.pop();
110 }
111 if is_casefolded {
113 hash_code = fscrypt::direntry::casefold_encrypt_hash_filename(
114 filename.as_slice(),
115 &decryptor.dirhash_key(),
116 );
117 };
118
119 let target = dentry[i].hash_code;
120 debug_assert_eq!(target, hash_code);
121 let filename_len = filename.len();
122 String::from_utf8(filename)
123 .unwrap_or_else(|_| format!("BAD_ENCRYPTED_FILENAME_len_{filename_len}"))
124 } else {
125 let hash_code = entry.hash_code as u64;
126 ProxyFilename::new(hash_code, &raw_filename).into()
127 }
128 } else {
129 str::from_utf8(&raw_filename).context("Bad UTF8 filename")?.to_string()
130 };
131 let file_type = FileType::n(dentry[i].file_type).ok_or_else(|| anyhow!("Bad file type"))?;
132 out.push(DirEntry {
133 hash_code: entry.hash_code,
134 ino: entry.ino,
135 filename,
136 file_type,
137 raw_filename,
138 });
139 i += (name_len + NAME_LEN - 1) / NAME_LEN;
140 }
141 Ok(out)
142}
143
144impl DentryBlock {
145 pub fn get_entries(
146 &self,
147 ino: u32,
148 is_encrypted: bool,
149 is_casefolded: bool,
150 decryptor: &Option<PerFileDecryptor>,
151 ) -> Result<Vec<DirEntry>, Error> {
152 get_dir_entries(
153 ino,
154 &self.dentry_bitmap,
155 &self.dentry,
156 &self.filenames,
157 is_encrypted,
158 is_casefolded,
159 decryptor,
160 )
161 }
162}
163
164impl crate::inode::Inode {
165 pub fn get_inline_dir_entries(
166 &self,
167 is_encrypted: bool,
168 is_casefolded: bool,
169 decryptor: &Option<PerFileDecryptor>,
170 ) -> Result<Option<Vec<DirEntry>>, Error> {
171 if let Some(inline_dentry) = &self.inline_dentry {
172 Ok(Some(get_dir_entries(
173 self.footer.ino,
174 &inline_dentry.dentry_bitmap,
175 &inline_dentry.dentry,
176 &inline_dentry.filenames,
177 is_encrypted,
178 is_casefolded,
179 decryptor,
180 )?))
181 } else {
182 Ok(None)
183 }
184 }
185}
186
187impl InlineDentry {
188 pub fn try_from_bytes(rest: &[u8]) -> Result<Self, Error> {
189 ensure!(rest.len() % 4 == 0, "Bad alignment in inode inline_dentry");
190 let rest = &rest[4..];
192 let dentry_count =
210 8 * rest.len() / (8 * (std::mem::size_of::<RawDirEntry>() + NAME_LEN) + 1);
211 let (dentry_bitmap, rest): (Ref<_, [u8]>, _) =
212 Ref::from_prefix_with_elems(rest, (dentry_count + 7) / 8).unwrap();
213 let (rest, filenames): (_, Ref<_, [u8]>) =
214 Ref::from_suffix_with_elems(rest, dentry_count * NAME_LEN).unwrap();
215 let (_, dentry): (_, Ref<_, [RawDirEntry]>) =
217 Ref::from_suffix_with_elems(rest, dentry_count).unwrap();
218 Ok(InlineDentry {
219 dentry_bitmap: (*dentry_bitmap).into(),
220 dentry: (*dentry).into(),
221 filenames: (*filenames).into(),
222 })
223 }
224}