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