1use crate::{
2 error::Ext4Error,
3 reader::FileReader,
4 structs::{
5 Descriptor, Directory, Ext4Hash, Extents, FileInfo, HashValue, Inode, InodeType, Stat,
6 },
7 superblock::block::{IncompatFlags, SuperBlock},
8};
9use log::error;
10use md5::{Digest, Md5};
11use sha1::Sha1;
12use sha2::Sha256;
13use std::{
14 collections::HashMap,
15 io::{BufReader, Read, copy},
16};
17
18pub struct Ext4Reader<T: std::io::Seek + std::io::Read> {
20 pub fs: BufReader<T>,
21 pub blocksize: u16,
23 pub offset_start: u64,
25 pub(crate) descriptors: Option<Vec<Descriptor>>,
26 pub(crate) incompat_flags: Vec<IncompatFlags>,
27 pub(crate) blocks_per_group: u32,
28 pub(crate) fs_size: u64,
29 pub(crate) number_blocks: u32,
30 pub(crate) inode_size: u16,
31 pub(crate) inodes_per_group: u32,
32 pub(crate) cache_names: HashMap<u64, String>,
33}
34
35pub trait Ext4ReaderAction<'ext4, 'reader, T: std::io::Seek + std::io::Read> {
36 fn root(&mut self) -> Result<FileInfo, Ext4Error>;
38 fn read_dir(&mut self, inode: u32) -> Result<FileInfo, Ext4Error>;
40 fn superblock(&mut self) -> Result<SuperBlock, Ext4Error>;
42 fn descriptors(&mut self) -> Result<Vec<Descriptor>, Ext4Error>;
44 fn extents(&mut self, inode: u32) -> Result<Option<Extents>, Ext4Error>;
46 fn stat(&mut self, inode: u32) -> Result<Stat, Ext4Error>;
48 fn hash(&mut self, inode: u32, hash: &Ext4Hash) -> Result<HashValue, Ext4Error>;
50 fn reader(&'reader mut self, inode: u32) -> Result<FileReader<'reader, T>, Ext4Error>;
52 fn read(&mut self, inode: u32) -> Result<Vec<u8>, Ext4Error>;
54 fn inode_verbose(&mut self, inode: u32) -> Result<Inode, Ext4Error>;
56}
57
58impl<T: std::io::Seek + std::io::Read> Ext4Reader<T> {
59 pub fn new(
61 fs: BufReader<T>,
62 blocksize: u16,
63 offset_start: u64,
64 ) -> Result<Ext4Reader<T>, Ext4Error> {
65 let mut reader = Ext4Reader {
66 fs,
67 blocksize,
68 offset_start,
69 descriptors: None,
70 incompat_flags: Vec::new(),
71 blocks_per_group: 0,
72 fs_size: 0,
73 number_blocks: 0,
74 inode_size: 0,
75 inodes_per_group: 0,
76 cache_names: HashMap::new(),
77 };
78
79 let block = SuperBlock::read_superblock(&mut reader.fs, reader.offset_start)?;
80 let size = 1024;
81 let base: u16 = 2;
82 reader.blocksize = size * base.pow(block.block_size);
83 reader.incompat_flags = block.incompatible_features_flags.clone();
84 reader.blocks_per_group = block.number_blocks_per_block_group;
85 reader.fs_size = block.number_blocks as u64 * blocksize as u64;
86 reader.number_blocks = block.number_blocks;
87 reader.inode_size = block.inode_size;
88 reader.inodes_per_group = block.number_inodes_per_block_group;
89 reader.descriptors = Some(Descriptor::read_descriptor(&mut reader)?);
90 Ok(reader)
91 }
92}
93
94impl<'ext4, 'reader, T: std::io::Seek + std::io::Read> Ext4ReaderAction<'ext4, 'reader, T>
95 for Ext4Reader<T>
96{
97 fn root(&mut self) -> Result<FileInfo, Ext4Error> {
98 let root_inode = 2;
99 self.read_dir(root_inode)
100 }
101
102 fn read_dir(&mut self, inode: u32) -> Result<FileInfo, Ext4Error> {
103 let inode_value = Inode::read_inode_table(self, inode)?;
104
105 if let Some(extent) = &inode_value.extents {
106 let dirs = Directory::read_directory_data(self, extent)?;
107 let mut info = FileInfo::new(inode_value, dirs, inode as u64);
108 if let Some(name) = self.cache_names.get(&info.inode) {
109 info.name = name.clone();
110 }
111 update_cache(&mut self.cache_names, &info);
112 return Ok(info);
113 }
114 error!("[ext4-fs] No extent data found. Cannot read directory");
115 Err(Ext4Error::Directory)
116 }
117
118 fn superblock(&mut self) -> Result<SuperBlock, Ext4Error> {
119 SuperBlock::read_superblock(&mut self.fs, self.offset_start)
120 }
121
122 fn stat(&mut self, inode: u32) -> Result<Stat, Ext4Error> {
123 let inode_value = Inode::read_inode_table(self, inode)?;
124 Ok(Stat::new(inode_value, inode as u64))
125 }
126
127 fn hash(&mut self, inode: u32, hashes: &Ext4Hash) -> Result<HashValue, Ext4Error> {
128 if !hashes.md5 && !hashes.sha1 && !hashes.sha256 {
129 return Ok(HashValue {
130 md5: String::new(),
131 sha1: String::new(),
132 sha256: String::new(),
133 });
134 }
135 let inode_value = Inode::read_inode_table(self, inode)?;
136 if inode_value.inode_type != InodeType::File {
137 return Err(Ext4Error::NotAFile);
138 }
139 let mut md5 = Md5::new();
140 let mut sha1 = Sha1::new();
141 let mut sha256 = Sha256::new();
142
143 let mut file_reader = self.reader(inode)?;
144 let mut bytes_read = 0;
146 let mut buf_size = 0;
148 let mut temp_buf_size = 65536;
150 loop {
151 let mut temp_buf = vec![0u8; temp_buf_size];
152 let bytes = match file_reader.read(&mut temp_buf) {
153 Ok(result) => result,
154 Err(err) => {
155 error!("[ext4-fs] Failed to read bytes for inode {inode}: {err:?}");
156 return Err(Ext4Error::FailedToRead);
157 }
158 };
159
160 if bytes == 0 {
162 break;
163 }
164
165 bytes_read += bytes;
166 if bytes_read > inode_value.size as usize {
167 temp_buf_size = bytes_read - inode_value.size as usize;
168 }
169
170 if bytes < temp_buf_size {
172 temp_buf = temp_buf[0..bytes].to_vec();
173 } else if bytes > inode_value.size as usize {
174 temp_buf = temp_buf[0..inode_value.size as usize].to_vec();
178 }
179
180 if bytes_read > inode_value.size as usize && inode_value.size as usize > buf_size {
183 temp_buf = temp_buf[0..(inode_value.size as usize - buf_size)].to_vec();
184 }
185 buf_size += temp_buf.len();
186
187 if hashes.md5 {
188 let _ = copy(&mut temp_buf.as_slice(), &mut md5);
189 }
190 if hashes.sha1 {
191 let _ = copy(&mut temp_buf.as_slice(), &mut sha1);
192 }
193 if hashes.sha256 {
194 let _ = copy(&mut temp_buf.as_slice(), &mut sha256);
195 }
196
197 if bytes_read >= inode_value.size as usize {
199 break;
200 }
201 }
202
203 let mut hash_value = HashValue {
204 md5: String::new(),
205 sha1: String::new(),
206 sha256: String::new(),
207 };
208
209 if hashes.md5 {
210 let hash = md5.finalize();
211 hash_value.md5 = format!("{hash:x}");
212 }
213 if hashes.sha1 {
214 let hash = sha1.finalize();
215 hash_value.sha1 = format!("{hash:x}");
216 }
217 if hashes.sha256 {
218 let hash = sha256.finalize();
219 hash_value.sha256 = format!("{hash:x}");
220 }
221
222 Ok(hash_value)
223 }
224
225 fn read(&mut self, inode: u32) -> Result<Vec<u8>, Ext4Error> {
226 let inode_value = Inode::read_inode_table(self, inode)?;
227 if inode_value.inode_type != InodeType::File {
228 return Err(Ext4Error::NotAFile);
229 }
230 let mut file_reader = self.reader(inode)?;
231 let mut buf = vec![0; inode_value.size as usize];
232 if let Err(err) = file_reader.read(&mut buf) {
233 error!("[ext4-fs] Could not read file: {err:?}");
234 return Err(Ext4Error::ReadFile);
235 }
236
237 Ok(buf)
238 }
239
240 fn reader(&'reader mut self, inode: u32) -> Result<FileReader<'reader, T>, Ext4Error> {
241 let inode_value = Inode::read_inode_table(self, inode)?;
242 if inode_value.inode_type != InodeType::File {
243 return Err(Ext4Error::NotAFile);
244 }
245 if let Some(extent) = inode_value.extents {
246 return Ok(Ext4Reader::file_reader(self, &extent, inode_value.size));
247 }
248 error!("[ext4-fs] No extent data found. Cannot read directory");
249 Err(Ext4Error::Directory)
250 }
251
252 fn descriptors(&mut self) -> Result<Vec<Descriptor>, Ext4Error> {
253 Descriptor::read_descriptor(self)
254 }
255
256 fn extents(&mut self, inode: u32) -> Result<Option<Extents>, Ext4Error> {
257 let inode_value = Inode::read_inode_table(self, inode)?;
258 Ok(inode_value.extents)
259 }
260
261 fn inode_verbose(&mut self, inode: u32) -> Result<Inode, Ext4Error> {
262 Inode::read_inode_table(self, inode)
263 }
264}
265
266fn update_cache(cache: &mut HashMap<u64, String>, info: &FileInfo) {
267 for entry in &info.children {
268 if entry.inode as u64 == info.inode {
269 continue;
270 }
271 cache.insert(entry.inode as u64, entry.name.clone());
272 }
273}
274
275#[cfg(test)]
276mod tests {
277 use crate::{
278 extfs::{Ext4Reader, Ext4ReaderAction},
279 structs::{Ext4Hash, FileInfo, FileType},
280 };
281 use std::{collections::HashMap, fs::File, io::BufReader, path::PathBuf};
282
283 fn walk_dir<T: std::io::Seek + std::io::Read>(
284 info: &FileInfo,
285 reader: &mut Ext4Reader<T>,
286 cache: &mut HashMap<u64, String>,
287 ) {
288 for entry in &info.children {
289 if entry.file_type == FileType::Directory
290 && entry.name != "."
291 && entry.name != ".."
292 && entry.inode != 2
293 {
294 let info = reader.read_dir(entry.inode).unwrap();
295 cache_paths(cache, &info);
296 walk_dir(&info, reader, cache);
297 continue;
298 }
299 if entry.file_type == FileType::Directory {
300 continue;
301 }
302 }
303 }
304
305 fn cache_paths(cache: &mut HashMap<u64, String>, info: &FileInfo) {
306 for entry in &info.children {
307 if entry.file_type != FileType::Directory || entry.name == "." || entry.name == ".." {
308 continue;
309 }
310 if cache.contains_key(&(entry.inode as u64))
311 && entry.inode != 2
312 && entry.name != "."
313 && entry.name != ".."
314 {
315 continue;
316 }
317
318 let path = cache.get(&(info.inode as u64)).unwrap();
319
320 cache.insert(
321 entry.inode as u64,
322 format!("{}/{}", path, entry.name.clone()),
323 );
324 }
325 }
326
327 #[test]
328 fn test_read_ext4_root() {
329 let mut test_location = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
330 test_location.push("tests/images/test.img");
331 let reader = File::open(test_location.to_str().unwrap()).unwrap();
332 let buf = BufReader::new(reader);
333 let mut ext4_reader = Ext4Reader::new(buf, 4096, 0).unwrap();
334 let dir = ext4_reader.root().unwrap();
335
336 assert_eq!(dir.created, 1759689014000000000);
337 assert_eq!(dir.changed, 1759713496631583423);
338 assert_eq!(dir.children.len(), 6);
339 }
340
341 #[test]
342 fn test_read_ext4_dir() {
343 let mut test_location = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
344 test_location.push("tests/images/test.img");
345 let reader = File::open(test_location.to_str().unwrap()).unwrap();
346 let buf = BufReader::new(reader);
347 let mut ext4_reader = Ext4Reader::new(buf, 4096, 0).unwrap();
348 ext4_reader.root().unwrap();
349 let dir = ext4_reader.read_dir(7634).unwrap();
350
351 assert_eq!(dir.created, 1759689167899447083);
352 assert_eq!(dir.changed, 1759689170863467296);
353 assert_eq!(dir.children.len(), 10);
354 assert_eq!(dir.parent_inode, 2);
355 }
356
357 #[test]
358 fn test_read_ext4_index_dir() {
359 let mut test_location = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
360 test_location.push("tests/images/test.img");
361 let reader = File::open(test_location.to_str().unwrap()).unwrap();
362 let buf = BufReader::new(reader);
363 let mut ext4_reader = Ext4Reader::new(buf, 4096, 0).unwrap();
364 ext4_reader.root().unwrap();
365 let dir = ext4_reader.read_dir(7633).unwrap();
366
367 assert_eq!(dir.created, 1759689153355347892);
368 assert_eq!(dir.changed, 1759689156340368251);
369 assert_eq!(dir.children.len(), 165);
370 assert_eq!(dir.parent_inode, 2);
371 }
372
373 #[test]
374 fn test_walk_dir() {
375 let mut test_location = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
376 test_location.push("tests/images/test.img");
377 let reader = File::open(test_location.to_str().unwrap()).unwrap();
378 let buf = BufReader::new(reader);
379 let mut ext4_reader = Ext4Reader::new(buf, 4096, 0).unwrap();
380 let root = ext4_reader.root().unwrap();
381 let mut cache = HashMap::new();
382 cache.insert(2, String::from(""));
383 cache_paths(&mut cache, &root);
384 walk_dir(&root, &mut ext4_reader, &mut cache);
385 assert_eq!(cache.len(), 10);
386 }
387
388 #[test]
389 fn test_stat() {
390 let mut test_location = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
391 test_location.push("tests/images/test.img");
392 let reader = File::open(test_location.to_str().unwrap()).unwrap();
393 let buf = BufReader::new(reader);
394 let mut ext4_reader = Ext4Reader::new(buf, 4096, 0).unwrap();
395 let root = ext4_reader.root().unwrap();
396 let mut cache = HashMap::new();
397 cache.insert(2, String::from(""));
398 cache_paths(&mut cache, &root);
399 walk_dir(&root, &mut ext4_reader, &mut cache);
400
401 let info = ext4_reader.stat(16).unwrap();
402 assert_eq!(info.created, 1759689156064366369);
403 assert_eq!(info.changed, 1759689156065366375);
404 assert_eq!(info.accessed, 1759689156064366369);
405 assert_eq!(info.modified, 1676375355000000000);
406 assert_eq!(
407 info.extended_attributes.get("security.selinux").unwrap(),
408 "unconfined_u:object_r:unlabeled_t:s0"
409 );
410 }
411
412 #[test]
413 fn test_hash_large_file() {
414 let mut test_location = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
415 test_location.push("tests/images/test.img");
416 let reader = File::open(test_location.to_str().unwrap()).unwrap();
417 let buf = BufReader::new(reader);
418 let mut ext4_reader = Ext4Reader::new(buf, 4096, 0).unwrap();
419 let hashes = Ext4Hash {
420 md5: true,
421 sha1: true,
422 sha256: true,
423 };
424 let info = ext4_reader.hash(676, &hashes).unwrap();
425 assert_eq!(info.md5, "df8e85bd10b33ac804b7c46073768dc9");
426 assert_eq!(info.sha1, "beb51c72d95518720c76e69fd2ad5f7a57e01d6b");
427 assert_eq!(
428 info.sha256,
429 "703df175cdcbbe0163f4ed7c83819070630b8bffdf65dc5739caef062a9c7a73"
430 );
431 }
432
433 #[test]
434 fn test_read_large_file() {
435 let mut test_location = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
436 test_location.push("tests/images/test.img");
437 let reader = File::open(test_location.to_str().unwrap()).unwrap();
438 let buf = BufReader::new(reader);
439 let mut ext4_reader = Ext4Reader::new(buf, 4096, 0).unwrap();
440 let info = ext4_reader.read(676).unwrap();
441 assert_eq!(info.len(), 274310864);
442 }
443
444 #[test]
445 fn test_descriptors() {
446 let mut test_location = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
447 test_location.push("tests/images/test.img");
448 let reader = File::open(test_location.to_str().unwrap()).unwrap();
449 let buf = BufReader::new(reader);
450 let mut ext4_reader = Ext4Reader::new(buf, 4096, 0).unwrap();
451 let info = ext4_reader.descriptors().unwrap();
452 assert_eq!(info.len(), 7);
453 }
454
455 #[test]
456 fn test_extents() {
457 let mut test_location = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
458 test_location.push("tests/images/test.img");
459 let reader = File::open(test_location.to_str().unwrap()).unwrap();
460 let buf = BufReader::new(reader);
461 let mut ext4_reader = Ext4Reader::new(buf, 4096, 0).unwrap();
462 let info = ext4_reader.extents(676).unwrap().unwrap();
463 assert_eq!(info.extent_descriptors.len(), 3);
464 }
465}