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