1use std::cmp::max;
15use std::collections::BTreeMap;
16use std::sync::{Arc, RwLock};
17
18use keramics_core::{DataStream, DataStreamReference, ErrorTrace, FakeDataStream};
19use keramics_datetime::DateTime;
20use keramics_types::{ByteString, bytes_to_u16_le};
21
22use super::block_stream::ExtBlockStream;
23use super::constants::*;
24use super::directory_entry::ExtDirectoryEntry;
25use super::directory_tree::ExtDirectoryTree;
26use super::inode::ExtInode;
27use super::inode_table::ExtInodeTable;
28
29pub struct ExtFileEntry {
31 data_stream: DataStreamReference,
33
34 inode_table: Arc<ExtInodeTable>,
36
37 pub inode_number: u32,
39
40 inode: ExtInode,
42
43 name: Option<ByteString>,
45
46 directory_entries: BTreeMap<ByteString, ExtDirectoryEntry>,
48
49 read_directory_entries: bool,
51
52 symbolic_link_target: Option<ByteString>,
54}
55
56impl ExtFileEntry {
57 pub(super) fn new(
59 data_stream: &DataStreamReference,
60 inode_table: &Arc<ExtInodeTable>,
61 inode_number: u32,
62 inode: ExtInode,
63 name: Option<ByteString>,
64 ) -> Self {
65 Self {
66 data_stream: data_stream.clone(),
67 inode_table: inode_table.clone(),
68 inode_number: inode_number,
69 inode: inode,
70 name: name,
71 directory_entries: BTreeMap::new(),
72 read_directory_entries: false,
73 symbolic_link_target: None,
74 }
75 }
76
77 pub fn get_access_time(&self) -> Option<&DateTime> {
79 Some(&self.inode.access_time)
80 }
81
82 pub fn get_change_time(&self) -> Option<&DateTime> {
84 Some(&self.inode.change_time)
85 }
86
87 pub fn get_creation_time(&self) -> Option<&DateTime> {
89 self.inode.creation_time.as_ref()
90 }
91
92 pub fn get_deletion_time(&self) -> &DateTime {
94 &self.inode.deletion_time
95 }
96
97 pub fn get_device_identifier(&mut self) -> Result<Option<u16>, ErrorTrace> {
99 if self.inode.file_mode & 0xf000 == EXT_FILE_MODE_TYPE_CHARACTER_DEVICE
100 || self.inode.file_mode & 0xf000 == EXT_FILE_MODE_TYPE_BLOCK_DEVICE
101 {
102 if self.inode.data_size > 2 {
103 return Err(keramics_core::error_trace_new!(format!(
104 "Invalid device identifier data size: {} value out of bounds",
105 self.inode.data_size,
106 )));
107 }
108 let device_identifier: u16 = bytes_to_u16_le!(&self.inode.data_reference, 0);
109 return Ok(Some(device_identifier));
110 }
111 Ok(None)
112 }
113
114 pub fn get_file_mode(&self) -> u16 {
116 self.inode.file_mode
117 }
118
119 pub fn get_group_identifier(&self) -> u32 {
121 self.inode.group_identifier
122 }
123
124 pub fn get_modification_time(&self) -> Option<&DateTime> {
126 Some(&self.inode.modification_time)
127 }
128
129 pub fn get_name(&self) -> Option<&ByteString> {
131 self.name.as_ref()
132 }
133
134 pub fn get_number_of_links(&self) -> u16 {
136 self.inode.number_of_links
137 }
138
139 pub fn get_owner_identifier(&self) -> u32 {
141 self.inode.owner_identifier
142 }
143
144 pub fn get_size(&self) -> u64 {
146 match self.inode.file_mode & 0xf000 {
147 EXT_FILE_MODE_TYPE_REGULAR_FILE | EXT_FILE_MODE_TYPE_SYMBOLIC_LINK => {
148 self.inode.data_size
149 }
150 _ => 0,
151 }
152 }
153
154 pub fn get_symbolic_link_target(&mut self) -> Result<Option<&ByteString>, ErrorTrace> {
156 if self.symbolic_link_target.is_none()
157 && self.inode.file_mode & 0xf000 == EXT_FILE_MODE_TYPE_SYMBOLIC_LINK
158 {
159 if self.inode.data_size > (self.inode_table.block_size as u64) {
160 return Err(keramics_core::error_trace_new!(format!(
161 "Invalid symbolic link target data size: {} value out of bounds",
162 self.inode.data_size,
163 )));
164 }
165 let byte_string: ByteString = if self.inode.data_size < 60 {
166 ByteString::from(self.inode.data_reference.as_slice())
167 } else {
168 let number_of_blocks: u64 = max(
169 self.inode
170 .data_size
171 .div_ceil(self.inode_table.block_size as u64),
172 self.inode.number_of_blocks,
173 );
174 let mut block_stream: ExtBlockStream =
175 ExtBlockStream::new(self.inode_table.block_size, self.inode.data_size);
176
177 match block_stream.open(
178 &self.data_stream,
179 number_of_blocks,
180 &self.inode.block_ranges,
181 ) {
182 Ok(_) => {}
183 Err(mut error) => {
184 keramics_core::error_trace_add_frame!(error, "Unable to open block stream");
185 return Err(error);
186 }
187 }
188 let mut data: Vec<u8> = vec![0; self.inode.data_size as usize];
189
190 match block_stream.read_exact(&mut data) {
191 Ok(_) => {}
192 Err(mut error) => {
193 keramics_core::error_trace_add_frame!(
194 error,
195 "Unable to read symbolic link target data from block stream"
196 );
197 return Err(error);
198 }
199 }
200 ByteString::from(&data)
201 };
202 self.symbolic_link_target = Some(byte_string);
203 }
204 Ok(self.symbolic_link_target.as_ref())
205 }
206
207 pub fn get_number_of_attributes(&mut self) -> Result<usize, ErrorTrace> {
209 Ok(self.inode.attributes.len())
210 }
211
212 pub fn get_data_stream(&self) -> Result<Option<DataStreamReference>, ErrorTrace> {
216 if self.inode.file_mode & 0xf000 != EXT_FILE_MODE_TYPE_REGULAR_FILE {
217 return Ok(None);
218 }
219 if self.inode.flags & EXT_INODE_FLAG_INLINE_DATA != 0 {
220 let data_stream: FakeDataStream =
221 FakeDataStream::new(&self.inode.data_reference, self.inode.data_size);
222 Ok(Some(Arc::new(RwLock::new(data_stream))))
223 } else {
224 let number_of_blocks: u64 = max(
225 self.inode
226 .data_size
227 .div_ceil(self.inode_table.block_size as u64),
228 self.inode.number_of_blocks,
229 );
230 let mut block_stream: ExtBlockStream =
231 ExtBlockStream::new(self.inode_table.block_size, self.inode.data_size);
232
233 match block_stream.open(
234 &self.data_stream,
235 number_of_blocks,
236 &self.inode.block_ranges,
237 ) {
238 Ok(_) => {}
239 Err(mut error) => {
240 keramics_core::error_trace_add_frame!(error, "Unable to open block stream");
241 return Err(error);
242 }
243 }
244 Ok(Some(Arc::new(RwLock::new(block_stream))))
245 }
246 }
247
248 pub fn get_number_of_sub_file_entries(&mut self) -> Result<usize, ErrorTrace> {
250 if !self.read_directory_entries {
251 match self.read_directory_entries() {
252 Ok(_) => {}
253 Err(mut error) => {
254 keramics_core::error_trace_add_frame!(
255 error,
256 "Unable to read directory entries"
257 );
258 return Err(error);
259 }
260 }
261 }
262 Ok(self.directory_entries.len())
263 }
264
265 pub fn get_sub_file_entry_by_index(
267 &mut self,
268 sub_file_entry_index: usize,
269 ) -> Result<ExtFileEntry, ErrorTrace> {
270 if !self.read_directory_entries {
271 match self.read_directory_entries() {
272 Ok(_) => {}
273 Err(mut error) => {
274 keramics_core::error_trace_add_frame!(
275 error,
276 "Unable to read directory entries"
277 );
278 return Err(error);
279 }
280 }
281 }
282 let (name, directory_entry): (&ByteString, &ExtDirectoryEntry) =
283 match self.directory_entries.iter().nth(sub_file_entry_index) {
284 Some(key_and_value) => key_and_value,
285 None => {
286 return Err(keramics_core::error_trace_new!(format!(
287 "Missing directory entry: {}",
288 sub_file_entry_index
289 )));
290 }
291 };
292 let inode: ExtInode = match self
293 .inode_table
294 .get_inode(&self.data_stream, directory_entry.inode_number)
295 {
296 Ok(inode) => inode,
297 Err(mut error) => {
298 keramics_core::error_trace_add_frame!(
299 error,
300 format!("Unable to retrieve inode: {}", directory_entry.inode_number)
301 );
302 return Err(error);
303 }
304 };
305 let file_entry: ExtFileEntry = ExtFileEntry::new(
306 &self.data_stream,
307 &self.inode_table,
308 directory_entry.inode_number,
309 inode,
310 Some(name.clone()),
311 );
312 Ok(file_entry)
313 }
314
315 pub fn get_sub_file_entry_by_name(
319 &mut self,
320 sub_file_entry_name: &ByteString,
321 ) -> Result<Option<ExtFileEntry>, ErrorTrace> {
322 if !self.read_directory_entries {
323 match self.read_directory_entries() {
324 Ok(_) => {}
325 Err(mut error) => {
326 keramics_core::error_trace_add_frame!(
327 error,
328 "Unable to read directory entries"
329 );
330 return Err(error);
331 }
332 }
333 }
334 let (name, directory_entry): (&ByteString, &ExtDirectoryEntry) =
335 match self.directory_entries.get_key_value(sub_file_entry_name) {
336 Some(key_and_value) => key_and_value,
337 None => return Ok(None),
338 };
339 let inode: ExtInode = match self
340 .inode_table
341 .get_inode(&self.data_stream, directory_entry.inode_number)
342 {
343 Ok(inode) => inode,
344 Err(mut error) => {
345 keramics_core::error_trace_add_frame!(
346 error,
347 format!("Unable to retrieve inode: {}", directory_entry.inode_number)
348 );
349 return Err(error);
350 }
351 };
352 let file_entry: ExtFileEntry = ExtFileEntry::new(
353 &self.data_stream,
354 &self.inode_table,
355 directory_entry.inode_number,
356 inode,
357 Some(name.clone()),
358 );
359 Ok(Some(file_entry))
360 }
361
362 pub fn is_root_directory(&self) -> bool {
364 self.inode_number == EXT_ROOT_DIRECTORY_IDENTIFIER
365 }
366
367 fn read_directory_entries(&mut self) -> Result<(), ErrorTrace> {
369 if self.inode.file_mode & 0xf000 == EXT_FILE_MODE_TYPE_DIRECTORY {
370 let mut directory_tree: ExtDirectoryTree =
371 ExtDirectoryTree::new(self.inode_table.block_size);
372
373 if self.inode.flags & EXT_INODE_FLAG_INLINE_DATA != 0 {
374 match directory_tree
375 .read_inline_data(&self.inode.data_reference, &mut self.directory_entries)
376 {
377 Ok(_) => {}
378 Err(mut error) => {
379 keramics_core::error_trace_add_frame!(
380 error,
381 "Unable to read directory entries from inline data"
382 );
383 return Err(error);
384 }
385 }
386 } else {
387 match directory_tree.read_block_data(
388 &self.data_stream,
389 &self.inode.block_ranges,
390 &mut self.directory_entries,
391 ) {
392 Ok(_) => {}
393 Err(mut error) => {
394 keramics_core::error_trace_add_frame!(
395 error,
396 "Unable to read directory entries"
397 );
398 return Err(error);
399 }
400 }
401 }
402 }
403 self.read_directory_entries = true;
404
405 Ok(())
406 }
407}
408
409#[cfg(test)]
410mod tests {
411 use super::*;
412
413 use std::path::PathBuf;
414
415 use keramics_core::open_os_data_stream;
416 use keramics_datetime::PosixTime32;
417
418 use crate::ext::file_system::ExtFileSystem;
419 use crate::ext::path::ExtPath;
420
421 fn get_file_system() -> Result<ExtFileSystem, ErrorTrace> {
422 let mut file_system: ExtFileSystem = ExtFileSystem::new();
423
424 let path_buf: PathBuf = PathBuf::from("../test_data/ext/ext2.raw");
425 let data_stream: DataStreamReference = open_os_data_stream(&path_buf)?;
426 file_system.read_data_stream(&data_stream)?;
427
428 Ok(file_system)
429 }
430
431 #[test]
432 fn test_get_access_time() -> Result<(), ErrorTrace> {
433 let ext_file_system: ExtFileSystem = get_file_system()?;
434
435 let ext_path: ExtPath = ExtPath::from("/testdir1/testfile1");
436 let ext_file_entry: ExtFileEntry =
437 ext_file_system.get_file_entry_by_path(&ext_path)?.unwrap();
438
439 assert_eq!(
440 ext_file_entry.get_access_time(),
441 Some(&DateTime::PosixTime32(PosixTime32 {
442 timestamp: 1735977482
443 }))
444 );
445
446 Ok(())
447 }
448
449 #[test]
450 fn test_get_change_time() -> Result<(), ErrorTrace> {
451 let ext_file_system: ExtFileSystem = get_file_system()?;
452
453 let ext_path: ExtPath = ExtPath::from("/testdir1/testfile1");
454 let ext_file_entry: ExtFileEntry =
455 ext_file_system.get_file_entry_by_path(&ext_path)?.unwrap();
456
457 assert_eq!(
458 ext_file_entry.get_change_time(),
459 Some(&DateTime::PosixTime32(PosixTime32 {
460 timestamp: 1735977481
461 }))
462 );
463
464 Ok(())
465 }
466
467 #[test]
468 fn test_get_creation_time() -> Result<(), ErrorTrace> {
469 let ext_file_system: ExtFileSystem = get_file_system()?;
470
471 let ext_path: ExtPath = ExtPath::from("/testdir1/testfile1");
472 let ext_file_entry: ExtFileEntry =
473 ext_file_system.get_file_entry_by_path(&ext_path)?.unwrap();
474
475 assert_eq!(ext_file_entry.get_creation_time(), None);
476
477 Ok(())
478 }
479
480 #[test]
481 fn test_get_device_identifier() -> Result<(), ErrorTrace> {
482 let ext_file_system: ExtFileSystem = get_file_system()?;
483
484 let ext_path: ExtPath = ExtPath::from("/testdir1/testfile1");
485 let mut ext_file_entry: ExtFileEntry =
486 ext_file_system.get_file_entry_by_path(&ext_path)?.unwrap();
487
488 let device_identifier: Option<u16> = ext_file_entry.get_device_identifier()?;
489 assert_eq!(device_identifier, None);
490
491 Ok(())
494 }
495
496 #[test]
497 fn test_get_file_mode() -> Result<(), ErrorTrace> {
498 let ext_file_system: ExtFileSystem = get_file_system()?;
499
500 let ext_path: ExtPath = ExtPath::from("/testdir1/testfile1");
501 let ext_file_entry: ExtFileEntry =
502 ext_file_system.get_file_entry_by_path(&ext_path)?.unwrap();
503
504 assert_eq!(ext_file_entry.get_file_mode(), 0o100644);
505
506 Ok(())
507 }
508
509 #[test]
510 fn test_get_group_identifier() -> Result<(), ErrorTrace> {
511 let ext_file_system: ExtFileSystem = get_file_system()?;
512
513 let ext_path: ExtPath = ExtPath::from("/testdir1/testfile1");
514 let ext_file_entry: ExtFileEntry =
515 ext_file_system.get_file_entry_by_path(&ext_path)?.unwrap();
516
517 assert_eq!(ext_file_entry.get_group_identifier(), 1000);
518
519 Ok(())
520 }
521
522 #[test]
523 fn test_get_deletion_time() -> Result<(), ErrorTrace> {
524 let ext_file_system: ExtFileSystem = get_file_system()?;
525
526 let ext_path: ExtPath = ExtPath::from("/testdir1/testfile1");
527 let ext_file_entry: ExtFileEntry =
528 ext_file_system.get_file_entry_by_path(&ext_path)?.unwrap();
529
530 assert_eq!(ext_file_entry.get_deletion_time(), &DateTime::NotSet);
531
532 Ok(())
533 }
534
535 #[test]
536 fn test_get_modification_time() -> Result<(), ErrorTrace> {
537 let ext_file_system: ExtFileSystem = get_file_system()?;
538
539 let ext_path: ExtPath = ExtPath::from("/testdir1/testfile1");
540 let ext_file_entry: ExtFileEntry =
541 ext_file_system.get_file_entry_by_path(&ext_path)?.unwrap();
542
543 assert_eq!(
544 ext_file_entry.get_modification_time(),
545 Some(&DateTime::PosixTime32(PosixTime32 {
546 timestamp: 1735977481
547 }))
548 );
549
550 Ok(())
551 }
552
553 #[test]
554 fn test_get_name() -> Result<(), ErrorTrace> {
555 let ext_file_system: ExtFileSystem = get_file_system()?;
556
557 let ext_path: ExtPath = ExtPath::from("/testdir1/testfile1");
558 let ext_file_entry: ExtFileEntry =
559 ext_file_system.get_file_entry_by_path(&ext_path)?.unwrap();
560
561 let name: &ByteString = ext_file_entry.get_name().unwrap();
562 assert_eq!(name.to_string(), "testfile1");
563
564 Ok(())
565 }
566
567 #[test]
568 fn test_get_number_of_links() -> Result<(), ErrorTrace> {
569 let ext_file_system: ExtFileSystem = get_file_system()?;
570
571 let ext_path: ExtPath = ExtPath::from("/testdir1/testfile1");
572 let ext_file_entry: ExtFileEntry =
573 ext_file_system.get_file_entry_by_path(&ext_path)?.unwrap();
574
575 assert_eq!(ext_file_entry.get_number_of_links(), 2);
576
577 Ok(())
578 }
579
580 #[test]
581 fn test_get_owner_identifier() -> Result<(), ErrorTrace> {
582 let ext_file_system: ExtFileSystem = get_file_system()?;
583
584 let ext_path: ExtPath = ExtPath::from("/testdir1/testfile1");
585 let ext_file_entry: ExtFileEntry =
586 ext_file_system.get_file_entry_by_path(&ext_path)?.unwrap();
587
588 assert_eq!(ext_file_entry.get_owner_identifier(), 1000);
589
590 Ok(())
591 }
592
593 #[test]
594 fn test_get_size() -> Result<(), ErrorTrace> {
595 let ext_file_system: ExtFileSystem = get_file_system()?;
596
597 let ext_path: ExtPath = ExtPath::from("/testdir1/testfile1");
598 let ext_file_entry: ExtFileEntry =
599 ext_file_system.get_file_entry_by_path(&ext_path)?.unwrap();
600
601 assert_eq!(ext_file_entry.get_size(), 9);
602
603 Ok(())
604 }
605
606 #[test]
607 fn test_get_symbolic_link_target() -> Result<(), ErrorTrace> {
608 let ext_file_system: ExtFileSystem = get_file_system()?;
609
610 let ext_path: ExtPath = ExtPath::from("/testdir1/testfile1");
611 let mut ext_file_entry: ExtFileEntry =
612 ext_file_system.get_file_entry_by_path(&ext_path)?.unwrap();
613
614 let symbolic_link_target: Option<&ByteString> =
615 ext_file_entry.get_symbolic_link_target()?;
616 assert_eq!(symbolic_link_target, None);
617
618 Ok(())
621 }
622
623 #[test]
624 fn test_get_data_stream() -> Result<(), ErrorTrace> {
625 let ext_file_system: ExtFileSystem = get_file_system()?;
626
627 let ext_path: ExtPath = ExtPath::from("/testdir1/testfile1");
628 let ext_file_entry: ExtFileEntry =
629 ext_file_system.get_file_entry_by_path(&ext_path)?.unwrap();
630
631 ext_file_entry.get_data_stream()?;
632
633 Ok(())
634 }
635
636 #[test]
637 fn test_get_number_of_attributes() -> Result<(), ErrorTrace> {
638 let ext_file_system: ExtFileSystem = get_file_system()?;
639
640 let ext_path: ExtPath = ExtPath::from("/testdir1/testfile1");
641 let mut ext_file_entry: ExtFileEntry =
642 ext_file_system.get_file_entry_by_path(&ext_path)?.unwrap();
643
644 let number_of_attributes: usize = ext_file_entry.get_number_of_attributes()?;
645 assert_eq!(number_of_attributes, 0);
646
647 Ok(())
650 }
651
652 #[test]
653 fn test_get_number_of_sub_file_entries() -> Result<(), ErrorTrace> {
654 let ext_file_system: ExtFileSystem = get_file_system()?;
655
656 let ext_path: ExtPath = ExtPath::from("/testdir1/testfile1");
657 let mut ext_file_entry: ExtFileEntry =
658 ext_file_system.get_file_entry_by_path(&ext_path)?.unwrap();
659
660 let number_of_sub_file_entries: usize = ext_file_entry.get_number_of_sub_file_entries()?;
661 assert_eq!(number_of_sub_file_entries, 0);
662
663 Ok(())
666 }
667
668 }