1use crate::parser::Parser;
6use crate::readers::ReaderWriter;
7use crate::structs::{FIRST_BG_PADDING, InvalidAddressErrorType, ParsingError};
8use fuchsia_sync::Mutex;
9use futures::future::FutureExt;
10use std::sync::Arc;
11
12#[derive(Default)]
13pub struct Ext4FileMetrics {
14 num_read_requests: u64,
15 num_open_requests: u64,
16 num_truncate_requests: u64,
17 num_write_requests: u64,
18 num_writes_past_eof_attempts: u64,
19 num_successful_overwrites: u64,
20 num_blocks_overwritten: u64,
21}
22
23pub struct Ext4Processor {
25 fs: Parser,
26 reader_writer: Arc<dyn ReaderWriter>,
27 read_only: bool,
28 file_metrics: Arc<Mutex<Ext4FileMetrics>>,
29}
30
31impl std::ops::Deref for Ext4Processor {
32 type Target = Parser;
33
34 fn deref(&self) -> &Self::Target {
35 &self.fs
36 }
37}
38
39impl Ext4Processor {
40 pub fn new(reader_writer: Arc<dyn ReaderWriter>, read_only: bool) -> Self {
41 Self {
42 fs: Parser::new(Box::new(reader_writer.clone())),
43 reader_writer,
44 read_only,
45 file_metrics: Arc::new(Mutex::new(Ext4FileMetrics::default())),
46 }
47 }
48
49 pub fn record_read_metrics(&self) {
50 let mut metrics = self.file_metrics.lock();
51 metrics.num_read_requests += 1;
52 }
53
54 pub fn record_open_metrics(&self) {
55 let mut metrics = self.file_metrics.lock();
56 metrics.num_open_requests += 1;
57 }
58
59 pub fn record_statistics(&self, stats_node: &fuchsia_inspect::Node) {
60 let metrics = self.file_metrics.clone();
61 stats_node.record_lazy_child("file_metrics", move || {
62 let metrics = metrics.clone();
63 async move {
64 let inspector = fuchsia_inspect::Inspector::default();
65 let root = inspector.root();
66 let metrics = metrics.lock();
67 root.record_uint("num_read_requests", metrics.num_read_requests);
68 root.record_uint("num_open_requests", metrics.num_open_requests);
69 root.record_uint("num_truncate_requests", metrics.num_truncate_requests);
70 root.record_uint("num_write_requests", metrics.num_write_requests);
71 root.record_uint(
72 "num_writes_past_eof_attempts",
73 metrics.num_writes_past_eof_attempts,
74 );
75 root.record_uint("num_successful_overwrites", metrics.num_successful_overwrites);
76 root.record_uint("num_blocks_overwritten", metrics.num_blocks_overwritten);
77 Ok(inspector)
78 }
79 .boxed()
80 });
81 }
82
83 pub fn read_only(&self) -> bool {
84 self.read_only
85 }
86
87 fn write_blocks(&self, block_number: u64, data: &[u8]) -> Result<(), ParsingError> {
89 if self.read_only {
90 return Err(ParsingError::Incompatible("Cannot write to read-only Ext4".to_string()));
91 }
92 if block_number == 0 {
93 return Err(ParsingError::InvalidAddress(
94 InvalidAddressErrorType::Lower,
95 0,
96 FIRST_BG_PADDING,
97 ));
98 }
99
100 let block_size = self.block_size()?;
101 if data.len() as u64 % block_size != 0 {
102 return Err(ParsingError::Incompatible(format!(
103 "Data length {} is not a multiple of block size {}",
104 data.len(),
105 block_size
106 )));
107 }
108
109 let address = block_number
110 .checked_mul(block_size)
111 .ok_or(ParsingError::BlockNumberOutOfBounds(block_number))?;
112
113 self.reader_writer.write(address, data)?;
114
115 Ok(())
116 }
117
118 pub fn truncate(&self, _length: u64) -> Result<(), ParsingError> {
119 let mut file_metrics = self.file_metrics.lock();
120 file_metrics.num_truncate_requests += 1;
121 return Err(ParsingError::NotSupported("truncate".to_string()));
123 }
124
125 pub fn overwrite_file_contents(
128 &self,
129 inode_num: u32,
130 data: impl AsRef<[u8]>,
131 offset: u64,
132 ) -> Result<(), ParsingError> {
133 if self.read_only {
134 return Err(ParsingError::Incompatible("Cannot write to read-only Ext4".to_string()));
135 }
136 let mut file_metrics = self.file_metrics.lock();
137 file_metrics.num_write_requests += 1;
138
139 let inode = self.inode(inode_num)?;
140 if offset + data.as_ref().len() as u64 > inode.size() {
142 file_metrics.num_writes_past_eof_attempts += 1;
143 return Err(ParsingError::NotSupported("writing past EOF".to_string()));
144 }
145 if data.as_ref().len() == 0 {
146 file_metrics.num_successful_overwrites += 1;
147 return Ok(());
148 }
149
150 let root_extent_tree_node = inode.extent_tree_node()?;
151 let request = offset..offset + data.as_ref().len() as u64;
152 let block_size = self.block_size()?;
153
154 self.iterate_extents_in_tree(&root_extent_tree_node, &mut |extent| {
155 let range = (extent.e_blk.get() as u64 * block_size)
156 ..((extent.e_blk.get() as u64 + extent.e_len.get() as u64) * block_size);
157 let overlap =
158 std::cmp::max(range.start, request.start)..std::cmp::min(range.end, request.end);
159 if overlap.start >= overlap.end {
160 return Ok(());
162 }
163
164 let mut physical_block_cursor =
165 extent.target_block_num() + ((overlap.start - range.start) / block_size);
166 let mut current_offset = overlap.start;
167 while current_offset < overlap.end {
168 let write_buf_cursor = (current_offset - request.start) as usize;
169 let block_off = current_offset % block_size;
170 let remaining_in_overlap = overlap.end - current_offset;
171
172 if block_off == 0 && remaining_in_overlap >= block_size {
173 let full_blocks = remaining_in_overlap / block_size;
175 let write_len = full_blocks * block_size;
176 self.write_blocks(
177 physical_block_cursor,
178 &data.as_ref()[write_buf_cursor..write_buf_cursor + write_len as usize],
179 )?;
180 file_metrics.num_blocks_overwritten += full_blocks;
181
182 physical_block_cursor += full_blocks;
183 current_offset += write_len;
184 } else {
185 let remaining_in_block = block_size - block_off;
188 let write_len = std::cmp::min(remaining_in_block, remaining_in_overlap);
189 let mut block_data = self.block(physical_block_cursor)?.into_vec();
190 block_data[block_off as usize..block_off as usize + write_len as usize]
191 .copy_from_slice(
192 &data.as_ref()[write_buf_cursor..write_buf_cursor + write_len as usize],
193 );
194 self.write_blocks(physical_block_cursor, &block_data)?;
195 file_metrics.num_blocks_overwritten += 1;
196
197 physical_block_cursor += 1;
198 current_offset += write_len;
199 }
200 }
201 Ok(())
202 })?;
203
204 file_metrics.num_successful_overwrites += 1;
207 Ok(())
208 }
209
210 pub fn sync(&self) -> Result<(), ParsingError> {
211 self.reader_writer.sync()?;
212 Ok(())
213 }
214}
215
216#[cfg(test)]
217mod tests {
218 use super::*;
219 use crate::readers::{BlockDeviceReader, VecReader};
220 use crate::structs::{FIRST_BG_PADDING, InvalidAddressErrorType, ParsingError};
221 use fidl_fuchsia_storage_block as fblock;
222 use fuchsia_async as fasync;
223 use std::fs;
224 use std::path::Path;
225 use vmo_backed_block_server::{InitialContents, VmoBackedServerOptions};
226 use zx::Vmo;
227
228 #[fuchsia::test]
229 async fn test_processor_read_only_blocks_write() {
230 let data = fs::read("/pkg/data/1file.img").expect("Unable to read file");
231 let read_only_processor = Ext4Processor::new(Arc::new(VecReader::new(data)), true);
232
233 let error = read_only_processor
234 .write_blocks(1, &[0u8; 1024])
235 .expect_err("passed write_blocks unexpectedly");
236 match error {
237 ParsingError::Incompatible(_) => {}
238 _ => panic!("Expected read-only error"),
239 }
240
241 let error = read_only_processor
243 .overwrite_file_contents(2, &[0u8; 10], 0)
244 .expect_err("passed overwrite_file_contents unexpectedly");
245 match error {
246 ParsingError::Incompatible(_) => {}
247 _ => panic!("Expected read-only error"),
248 }
249 }
250
251 #[fuchsia::test]
252 async fn test_processor_write_block_invalid_address() {
253 let data = fs::read("/pkg/data/1file.img").expect("Unable to read file");
254 let processor = Ext4Processor::new(Arc::new(VecReader::new(data)), false);
255
256 let error =
257 processor.write_blocks(0, &[0u8; 1024]).expect_err("passed write_blocks unexpectedly");
258 match error {
259 ParsingError::InvalidAddress(InvalidAddressErrorType::Lower, 0, FIRST_BG_PADDING) => {}
260 _ => panic!("Expected invalid address error, got {:?}", error),
261 }
262 }
263
264 #[fuchsia::test]
265 async fn test_processor_write_block_out_of_bounds() {
266 let data = fs::read("/pkg/data/1file.img").expect("Unable to read file");
267 let processor = Ext4Processor::new(Arc::new(VecReader::new(data)), false);
268
269 let error = processor
270 .write_blocks(u64::MAX, &[0u8; 1024])
271 .expect_err("passed write_blocks unexpectedly");
272 match error {
273 ParsingError::BlockNumberOutOfBounds(u64::MAX) => {}
274 _ => panic!("Expected out of bounds error, got {:?}", error),
275 }
276 }
277
278 #[fuchsia::test]
279 async fn test_processor_writeable_overwrite_extents() {
280 let data = fs::read("/pkg/data/1file.img").expect("Unable to read file");
281 let vmo = Vmo::create(data.len() as u64).expect("failed to create VMO");
282 vmo.write(data.as_slice(), 0).expect("failed to write to VMO");
283 let server = Arc::new(
284 VmoBackedServerOptions {
285 block_size: 512,
286 initial_contents: InitialContents::FromVmo(vmo),
287 ..Default::default()
288 }
289 .build()
290 .expect("build from VmoBackedServerOptions failed"),
291 );
292
293 let server_clone = server.clone();
294 let (block_client_end1, block_server_end1) =
295 fidl::endpoints::create_endpoints::<fblock::BlockMarker>();
296 std::thread::spawn(move || {
297 let mut executor = fasync::TestExecutor::new();
298 let _task =
299 executor.run_singlethreaded(server_clone.serve(block_server_end1.into_stream()));
300 });
301 let inspector = fuchsia_inspect::Inspector::default();
302 let rw_processor = Arc::new(Ext4Processor::new(
303 Arc::new(
304 BlockDeviceReader::from_client_end(block_client_end1)
305 .expect("failed to create block device reader"),
306 ),
307 false,
308 ));
309 rw_processor.record_statistics(inspector.root());
310
311 let file_ino = rw_processor
312 .entry_at_path(Path::new("file1"))
313 .expect("failed entry at path")
314 .e2d_ino
315 .get();
316
317 let mut expected = rw_processor.read_data(file_ino).expect("failed to read data");
318 assert_eq!(
319 str::from_utf8(expected.as_slice()).expect("failed to read data"),
320 "file1 contents.\n"
321 );
322 let original_size = rw_processor.inode(file_ino).expect("failed to read inode").size();
323 assert_eq!(original_size, expected.len() as u64);
324
325 rw_processor
326 .overwrite_file_contents(file_ino, &[1u8; 1], 1)
327 .expect("failed to overwrite extents");
328 expected[1] = 1;
329
330 let new_data = rw_processor.read_data(file_ino).expect("failed to read data");
331 assert_eq!(new_data, expected);
332 diagnostics_assertions::assert_data_tree!(inspector, root: {
333 file_metrics: {
334 num_open_requests: 0u64,
335 num_read_requests: 0u64,
336 num_truncate_requests: 0u64,
337 num_write_requests: 1u64,
338 num_writes_past_eof_attempts: 0u64,
339 num_successful_overwrites: 1u64,
340 num_blocks_overwritten: 1u64,
341 }
342 });
343
344 let error = rw_processor
347 .overwrite_file_contents(file_ino, &[1u8; 2], expected.len() as u64 + 2)
348 .expect_err("overwrite past EOF should fail");
349 match error {
350 ParsingError::NotSupported(_) => {}
351 _ => panic!("Expected NotSupported error, got {:?}", error),
352 }
353
354 let new_size = rw_processor.inode(file_ino).expect("failed to read inode").size();
356 assert_eq!(new_size, original_size);
357 diagnostics_assertions::assert_data_tree!(inspector, root: {
358 file_metrics: {
359 num_open_requests: 0u64,
360 num_read_requests: 0u64,
361 num_truncate_requests: 0u64,
362 num_write_requests: 2u64,
363 num_writes_past_eof_attempts: 1u64,
364 num_successful_overwrites: 1u64,
365 num_blocks_overwritten: 1u64,
366 }
367 });
368 }
369}