1#![forbid(unsafe_code)]
2
3use crate::error::Result;
4use crate::inode::InodeReader;
5use crate::ondisk::FileType;
6use std::io::{Read, Seek};
7
8#[derive(Debug, Clone)]
10pub struct SlackSpace {
11 pub ino: u64,
12 pub file_size: u64,
13 pub block: u64,
15 pub slack_offset: usize,
17 pub data: Vec<u8>,
19}
20
21pub fn read_slack_space<R: Read + Seek>(
23 reader: &mut InodeReader<R>,
24 ino: u64,
25) -> Result<Option<SlackSpace>> {
26 let inode = reader.read_inode(ino)?;
27
28 if inode.file_type() != FileType::RegularFile {
30 return Ok(None);
31 }
32 if inode.size == 0 {
33 return Ok(None);
34 }
35
36 let block_size = u64::from(reader.block_reader().superblock().block_size);
37
38 if inode.size % block_size == 0 {
40 return Ok(None);
41 }
42
43 let mappings = match reader.inode_block_map(ino) {
45 Ok(m) => m,
46 Err(_) => return Ok(None),
47 };
48
49 if mappings.is_empty() {
50 return Ok(None);
51 }
52
53 let last_mapping = &mappings[mappings.len() - 1];
55 let last_phys_block = last_mapping.physical_block + last_mapping.length - 1;
56
57 let block_data = reader.block_reader_mut().read_block(last_phys_block)?;
59
60 let slack_offset = (inode.size % block_size) as usize;
61 let slack_data = block_data[slack_offset..].to_vec();
62
63 Ok(Some(SlackSpace {
64 ino,
65 file_size: inode.size,
66 block: last_phys_block,
67 slack_offset,
68 data: slack_data,
69 }))
70}
71
72pub fn scan_all_slack<R: Read + Seek>(reader: &mut InodeReader<R>) -> Result<Vec<SlackSpace>> {
74 let all_inodes = reader.iter_all_inodes()?;
75 let mut results = Vec::new();
76
77 for (ino, inode) in &all_inodes {
78 if inode.file_type() != FileType::RegularFile || inode.size == 0 {
79 continue;
80 }
81 if let Ok(Some(slack)) = read_slack_space(reader, *ino) {
82 results.push(slack);
83 }
84 }
85
86 Ok(results)
87}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92 use crate::block::BlockReader;
93 use std::io::Cursor;
94
95 fn open_minimal() -> Option<InodeReader<Cursor<Vec<u8>>>> {
96 let path = concat!(env!("CARGO_MANIFEST_DIR"), "/../tests/data/minimal.img");
97 let data = std::fs::read(path).ok()?;
98 let br = BlockReader::open(Cursor::new(data)).ok()?;
99 Some(InodeReader::new(br))
100 }
101
102 fn open_forensic() -> Option<InodeReader<Cursor<Vec<u8>>>> {
103 let path = concat!(env!("CARGO_MANIFEST_DIR"), "/../tests/data/forensic.img");
104 let data = std::fs::read(path).ok()?;
105 let br = BlockReader::open(Cursor::new(data)).ok()?;
106 Some(InodeReader::new(br))
107 }
108
109 #[test]
110 fn slack_space_for_small_file() {
111 let mut reader = if let Some(r) = open_minimal() {
112 r
113 } else {
114 eprintln!("skip");
115 return;
116 };
117 let all = reader.iter_all_inodes().unwrap();
119 let hello_ino = all
120 .iter()
121 .find(|(_, inode)| {
122 inode.file_type() == crate::ondisk::FileType::RegularFile && inode.size == 12
123 })
124 .map(|(ino, _)| *ino)
125 .expect("minimal.img should have a 12-byte regular file (hello.txt)");
126
127 let slack = read_slack_space(&mut reader, hello_ino).unwrap();
128 assert!(slack.is_some(), "small file should have slack");
129 let s = slack.unwrap();
130 assert_eq!(s.ino, hello_ino);
131 assert_eq!(s.file_size, 12);
132 let block_size = reader.block_reader().superblock().block_size as usize;
133 assert_eq!(s.data.len(), block_size - 12);
134 assert_eq!(s.slack_offset, 12);
135 }
136
137 #[test]
138 fn no_slack_for_zero_size_file() {
139 let mut reader = if let Some(r) = open_minimal() {
140 r
141 } else {
142 eprintln!("skip");
143 return;
144 };
145 let result = read_slack_space(&mut reader, 0);
147 assert!(result.is_err() || result.unwrap().is_none());
148 }
149
150 #[test]
151 fn scan_all_slack_finds_entries() {
152 let mut reader = if let Some(r) = open_forensic() {
153 r
154 } else {
155 eprintln!("skip");
156 return;
157 };
158 let slacks = scan_all_slack(&mut reader).unwrap();
159 assert!(
160 !slacks.is_empty(),
161 "forensic.img should have files with slack"
162 );
163 for s in &slacks {
164 assert!(!s.data.is_empty());
165 assert!(s.slack_offset > 0);
166 let block_size = reader.block_reader().superblock().block_size as usize;
167 assert_eq!(s.data.len() + s.slack_offset, block_size);
168 }
169 }
170}