1use crate::directory::ExtDirectory;
6use crate::file::ExtFile;
7use crate::types::ExtAttributes;
8use ext4_lib::processor::Ext4Processor;
9use ext4_lib::readers::{BlockDeviceReader, ReaderWriter, VmoReader};
10use ext4_lib::structs::{self, EntryType, MIN_EXT4_SIZE};
11use fidl::endpoints::ClientEnd;
12use fidl_fuchsia_storage_block::BlockMarker;
13use log::error;
14use std::sync::Arc;
15
16mod directory;
17mod file;
18mod node;
19mod types;
20
21pub enum FsSourceType {
22 BlockDevice(ClientEnd<BlockMarker>),
23 Vmo(zx::Vmo),
24}
25
26#[derive(Debug, PartialEq)]
27pub enum ConstructFsError {
28 VmoReadError(zx::Status),
29 ParsingError(structs::ParsingError),
30 FileVmoError(zx::Status),
31 NodeError(zx::Status),
32}
33
34impl From<structs::ParsingError> for ConstructFsError {
35 fn from(value: structs::ParsingError) -> Self {
36 Self::ParsingError(value)
37 }
38}
39
40pub fn construct_fs(
42 source: FsSourceType,
43 inspector: &fuchsia_inspect::Inspector,
44) -> Result<Arc<ExtDirectory>, ConstructFsError> {
45 construct_fs_internal(source, true, inspector)
47}
48
49fn construct_fs_internal(
50 source: FsSourceType,
51 read_only: bool,
52 inspector: &fuchsia_inspect::Inspector,
53) -> Result<Arc<ExtDirectory>, ConstructFsError> {
54 let reader: Arc<dyn ReaderWriter> = match source {
55 FsSourceType::BlockDevice(block_device) => {
56 Arc::new(BlockDeviceReader::from_client_end(block_device).map_err(|e| {
57 error!("Error constructing file system: {}", e);
58 ConstructFsError::VmoReadError(zx::Status::IO_INVALID)
59 })?)
60 }
61 FsSourceType::Vmo(vmo) => {
62 let size = vmo.get_size().map_err(ConstructFsError::VmoReadError)?;
63 if size < MIN_EXT4_SIZE as u64 {
64 return Err(ConstructFsError::VmoReadError(zx::Status::NO_SPACE));
66 }
67
68 Arc::new(VmoReader::new(Arc::new(vmo)))
69 }
70 };
71 let processor = Arc::new(Ext4Processor::new(reader, read_only));
72 let dir = build_fs_dir(processor.clone(), structs::ROOT_INODE_NUM, read_only)?;
73 processor.record_statistics(inspector.root());
74 Ok(dir)
75}
76
77fn build_fs_dir(
78 processor: Arc<Ext4Processor>,
79 ino: u32,
80 read_only: bool,
81) -> Result<Arc<ExtDirectory>, ConstructFsError> {
82 let inode = processor.inode(ino)?;
83 let entries = processor.entries_from_inode(&inode)?;
84 let attributes = ExtAttributes::from_inode(inode);
85 let xattrs = processor.inode_xattrs(ino)?;
86 let dir = ExtDirectory::new(ino as u64, attributes, xattrs);
87
88 for entry in entries {
89 let entry_name = entry.name()?;
90 if entry_name == "." || entry_name == ".." {
91 continue;
92 }
93
94 let entry_ino = u32::from(entry.e2d_ino);
95 match EntryType::from_u8(entry.e2d_type)? {
96 EntryType::Directory => {
97 dir.insert_child(
98 entry_name,
99 build_fs_dir(processor.clone(), entry_ino, read_only)?,
100 )
101 .map_err(ConstructFsError::NodeError)?;
102 }
103 EntryType::RegularFile => {
104 dir.insert_child(
105 entry_name,
106 ExtFile::from_processor(processor.clone(), entry_ino, read_only)
107 .map_err(ConstructFsError::NodeError)?,
108 )
109 .map_err(ConstructFsError::NodeError)?;
110 }
111 _ => {
112 }
114 }
115 }
116
117 Ok(dir)
118}
119
120#[cfg(test)]
121mod tests {
122 use super::{FsSourceType, construct_fs, construct_fs_internal};
123
124 use ext4_lib::structs::MIN_EXT4_SIZE;
125 use fidl_fuchsia_io as fio;
126 use fidl_fuchsia_storage_block as fblock;
127 use fuchsia_async as fasync;
128 use fuchsia_fs::directory::{DirEntry, DirentKind, open_file, open_node, readdir};
129 use fuchsia_fs::file::{WriteError, read_to_string, write};
130 use std::fs;
131 use std::sync::Arc;
132 use vmo_backed_block_server::{InitialContents, VmoBackedServerOptions};
133 use zx::{HandleBased, Status, Vmo};
134
135 #[fuchsia::test]
136 fn image_too_small() {
137 let vmo = Vmo::create(10).expect("VMO is created");
138 vmo.write(b"too small", 0).expect("VMO write() succeeds");
139 let buffer = FsSourceType::Vmo(vmo);
140
141 assert!(
142 construct_fs(buffer, &fuchsia_inspect::Inspector::default()).is_err(),
143 "Expected failed parsing of VMO."
144 );
145 }
146
147 #[fuchsia::test]
148 fn invalid_fs() {
149 let vmo = Vmo::create(MIN_EXT4_SIZE as u64).expect("VMO is created");
150 vmo.write(b"not ext4", 0).expect("VMO write() succeeds");
151 let buffer = FsSourceType::Vmo(vmo);
152
153 assert!(
154 construct_fs(buffer, &fuchsia_inspect::Inspector::default()).is_err(),
155 "Expected failed parsing of VMO."
156 );
157 }
158
159 #[fuchsia::test]
160 async fn list_root() {
161 let data = fs::read("/pkg/data/nest.img").expect("Unable to read file");
162 let vmo = Vmo::create(data.len() as u64).expect("VMO is created");
163 vmo.write(data.as_slice(), 0).expect("VMO write() succeeds");
164 let buffer = FsSourceType::Vmo(vmo);
165
166 let tree = construct_fs(buffer, &fuchsia_inspect::Inspector::default())
167 .expect("construct_fs parses the vmo");
168 let root = vfs::directory::serve(tree, fio::PERM_READABLE);
169
170 let expected = vec![
171 DirEntry { name: String::from("file1"), kind: DirentKind::File },
172 DirEntry { name: String::from("inner"), kind: DirentKind::Directory },
173 DirEntry { name: String::from("lost+found"), kind: DirentKind::Directory },
174 ];
175 assert_eq!(readdir(&root).await.unwrap(), expected);
176
177 let file = open_file(&root, "file1", fio::PERM_READABLE).await.unwrap();
178 assert_eq!(read_to_string(&file).await.unwrap(), "file1 contents.\n");
179 file.close().await.unwrap().map_err(zx::Status::from_raw).unwrap();
180 root.close().await.unwrap().map_err(zx::Status::from_raw).unwrap();
181 }
182
183 #[fuchsia::test]
184 async fn get_dac_attributes() {
185 let data = fs::read("/pkg/data/dac_attributes.img").expect("Unable to read file");
186 let vmo = Vmo::create(data.len() as u64).expect("VMO is created");
187 vmo.write(data.as_slice(), 0).expect("VMO write() succeeds");
188 let buffer = FsSourceType::Vmo(vmo);
189
190 let tree = construct_fs(buffer, &fuchsia_inspect::Inspector::default())
191 .expect("construct_fs parses the VMO");
192 let root = vfs::directory::serve(tree, fio::PERM_READABLE);
193
194 let expected_entries = vec![
195 DirEntry { name: String::from("dir_1000"), kind: DirentKind::Directory },
196 DirEntry { name: String::from("dir_root"), kind: DirentKind::Directory },
197 DirEntry { name: String::from("file_1000"), kind: DirentKind::File },
198 DirEntry { name: String::from("file_root"), kind: DirentKind::File },
199 DirEntry { name: String::from("lost+found"), kind: DirentKind::Directory },
200 ];
201 assert_eq!(readdir(&root).await.unwrap(), expected_entries);
202
203 #[derive(Debug, PartialEq)]
204 struct Node {
205 name: String,
206 mode: u32,
207 uid: u32,
208 gid: u32,
209 }
210
211 let expected_attributes = vec![
212 Node { name: String::from("dir_1000"), mode: 0x416D, uid: 1000, gid: 1000 },
213 Node { name: String::from("dir_root"), mode: 0x4140, uid: 0, gid: 0 },
214 Node { name: String::from("file_1000"), mode: 0x8124, uid: 1000, gid: 1000 },
215 Node { name: String::from("file_root"), mode: 0x8100, uid: 0, gid: 0 },
216 ];
217
218 let attributes_query = fio::NodeAttributesQuery::MODE
219 | fio::NodeAttributesQuery::UID
220 | fio::NodeAttributesQuery::GID;
221 for expected_node in &expected_attributes {
222 let node_proxy = open_node(&root, expected_node.name.as_str(), fio::PERM_READABLE)
223 .await
224 .expect("node open failed");
225 let (mut_attrs, _immut_attrs) = node_proxy
226 .get_attributes(attributes_query)
227 .await
228 .expect("node get_attributes() failed")
229 .map_err(Status::from_raw)
230 .expect("node get_attributes() error");
231
232 let node = Node {
233 name: expected_node.name.clone(),
234 mode: mut_attrs.mode.expect("node attributes missing mode"),
235 uid: mut_attrs.uid.expect("node attributes missing uid"),
236 gid: mut_attrs.gid.expect("node attributes missing gid"),
237 };
238
239 node_proxy
240 .close()
241 .await
242 .expect("node close failed")
243 .map_err(Status::from_raw)
244 .expect("node close error");
245
246 assert_eq!(node, *expected_node);
247 }
248
249 root.close().await.unwrap().map_err(Status::from_raw).unwrap();
250 }
251
252 #[fuchsia::test]
253 async fn test_constructing_writeable_fs_and_writing_to_allocated_region() {
254 let data = fs::read("/pkg/data/nest.img").expect("failed to read file");
256 let vmo = Vmo::create(data.len() as u64).expect("failed to create VMO");
257 vmo.write(data.as_slice(), 0).expect("failed to write to VMO");
258 let server = Arc::new(
259 VmoBackedServerOptions {
260 block_size: 512,
261 initial_contents: InitialContents::FromVmo(vmo),
262 ..Default::default()
263 }
264 .build()
265 .expect("build from VmoBackedServerOptions failed"),
266 );
267
268 let server_clone = server.clone();
269 let (block_client_end1, block_server_end1) =
270 fidl::endpoints::create_endpoints::<fblock::BlockMarker>();
271 std::thread::spawn(move || {
272 let mut executor = fasync::TestExecutor::new();
273 let _task =
274 executor.run_singlethreaded(server_clone.serve(block_server_end1.into_stream()));
275 });
276
277 let tree = construct_fs_internal(
279 FsSourceType::BlockDevice(block_client_end1),
280 false,
281 &fuchsia_inspect::Inspector::default(),
282 )
283 .expect("failed to parse the vmo");
284 let root = vfs::directory::serve(tree, fio::PERM_READABLE | fio::PERM_WRITABLE);
285 let file = open_file(&root, "file1", fio::PERM_READABLE | fio::PERM_WRITABLE)
286 .await
287 .expect("failed to open file");
288 let original_contents = "file1 contents.\n";
289 assert_eq!(read_to_string(&file).await.expect("failed to read file"), original_contents);
290 let new_contents = "NEW";
291 let offset = 5;
292 file.seek(fio::SeekOrigin::Start, offset)
293 .await
294 .expect("failed FIDL seek")
295 .map_err(zx::Status::from_raw)
296 .expect("failed to seek file");
297 write(&file, new_contents).await.expect("failed to write to file");
298 file.close()
299 .await
300 .expect("failed FIDL file close")
301 .map_err(zx::Status::from_raw)
302 .expect("failed to close file");
303 root.close()
304 .await
305 .expect("failed FIDL dir close")
306 .map_err(zx::Status::from_raw)
307 .expect("failed to close root");
308
309 let server_clone = server.clone();
311 let (block_client_end2, block_server_end2) =
312 fidl::endpoints::create_endpoints::<fblock::BlockMarker>();
313 std::thread::spawn(move || {
314 let mut executor = fasync::TestExecutor::new();
315 let _task =
316 executor.run_singlethreaded(server_clone.serve(block_server_end2.into_stream()));
317 });
318 let tree = construct_fs_internal(
319 FsSourceType::BlockDevice(block_client_end2),
320 true,
321 &fuchsia_inspect::Inspector::default(),
322 )
323 .expect("construct_fs parses the vmo");
324 let root = vfs::directory::serve(tree, fio::PERM_READABLE);
325 let file =
326 open_file(&root, "file1", fio::PERM_READABLE).await.expect("failed to open file");
327 let mut expected_bytes = original_contents.as_bytes().to_vec();
328 expected_bytes[offset as usize..offset as usize + new_contents.len()]
329 .copy_from_slice(new_contents.as_bytes());
330 assert_eq!(
331 read_to_string(&file).await.expect("failed to read file"),
332 String::from_utf8(expected_bytes).unwrap()
333 );
334 file.close()
335 .await
336 .expect("failed FIDL file close")
337 .map_err(zx::Status::from_raw)
338 .expect("failed to close file");
339 root.close()
340 .await
341 .expect("failed FIDL dir close")
342 .map_err(zx::Status::from_raw)
343 .expect("failed to close root");
344 }
345
346 #[fuchsia::test]
347 async fn test_writing_past_eof_fails() {
348 let data = fs::read("/pkg/data/nest.img").expect("failed to read file");
349 let vmo = Vmo::create(data.len() as u64).expect("failed to create VMO");
350 vmo.write(data.as_slice(), 0).expect("failed to write to VMO");
351 let server = Arc::new(
352 VmoBackedServerOptions {
353 block_size: 512,
354 initial_contents: InitialContents::FromVmo(vmo),
355 ..Default::default()
356 }
357 .build()
358 .expect("build from VmoBackedServerOptions failed"),
359 );
360
361 let server_clone = server.clone();
362 let (block_client_end1, block_server_end1) =
363 fidl::endpoints::create_endpoints::<fblock::BlockMarker>();
364 std::thread::spawn(move || {
365 let mut executor = fasync::TestExecutor::new();
366 let _task =
367 executor.run_singlethreaded(server_clone.serve(block_server_end1.into_stream()));
368 });
369
370 let tree = construct_fs_internal(
372 FsSourceType::BlockDevice(block_client_end1),
373 false,
374 &fuchsia_inspect::Inspector::default(),
375 )
376 .expect("failed to parse the vmo");
377 let root = vfs::directory::serve(tree, fio::PERM_READABLE | fio::PERM_WRITABLE);
378 let file = open_file(&root, "file1", fio::PERM_READABLE | fio::PERM_WRITABLE)
379 .await
380 .expect("failed to open file");
381 let original_contents = read_to_string(&file).await.expect("failed to read file");
382
383 let new_contents = [1u8; 8192];
385 file.seek(fio::SeekOrigin::Start, 0)
386 .await
387 .expect("failed FIDL seek")
388 .map_err(zx::Status::from_raw)
389 .expect("failed to seek file");
390 let error = write(&file, &new_contents)
391 .await
392 .expect_err("write to unallocated region passed unexpectedly");
393 match error {
394 WriteError::WriteError(status) => assert_eq!(status, zx::Status::NOT_SUPPORTED),
395 _ => panic!("Unexpected error: {:?}", error),
396 }
397
398 file.close()
399 .await
400 .expect("failed FIDL file close")
401 .map_err(zx::Status::from_raw)
402 .expect("failed to close file");
403 root.close()
404 .await
405 .expect("failed FIDL dir close")
406 .map_err(zx::Status::from_raw)
407 .expect("failed to close root");
408
409 let server_clone = server.clone();
411 let (block_client_end2, block_server_end2) =
412 fidl::endpoints::create_endpoints::<fblock::BlockMarker>();
413 std::thread::spawn(move || {
414 let mut executor = fasync::TestExecutor::new();
415 let _task =
416 executor.run_singlethreaded(server_clone.serve(block_server_end2.into_stream()));
417 });
418 let tree = construct_fs_internal(
419 FsSourceType::BlockDevice(block_client_end2),
420 true,
421 &fuchsia_inspect::Inspector::default(),
422 )
423 .expect("construct_fs parses the vmo");
424 let root = vfs::directory::serve(tree, fio::PERM_READABLE);
425 let file =
426 open_file(&root, "file1", fio::PERM_READABLE).await.expect("failed to open file");
427 assert_eq!(read_to_string(&file).await.expect("failed to read file"), original_contents);
428 file.close()
429 .await
430 .expect("failed FIDL file close")
431 .map_err(zx::Status::from_raw)
432 .expect("failed to close file");
433 root.close()
434 .await
435 .expect("failed FIDL dir close")
436 .map_err(zx::Status::from_raw)
437 .expect("failed to close root");
438 }
439
440 #[fuchsia::test]
441 async fn test_file_sync() {
442 let data = fs::read("/pkg/data/nest.img").expect("failed to read file");
443 let vmo = Vmo::create(data.len() as u64).expect("failed to create VMO");
444 vmo.write(data.as_slice(), 0).expect("failed to write to VMO");
445
446 let vmo_clone = vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).expect("failed to clone vmo");
448
449 let server = Arc::new(
450 VmoBackedServerOptions {
451 block_size: 512,
452 initial_contents: InitialContents::FromVmo(vmo),
453 ..Default::default()
454 }
455 .build()
456 .expect("build from VmoBackedServerOptions failed"),
457 );
458
459 let server_clone = server.clone();
460 let (block_client_end, block_server_end) =
461 fidl::endpoints::create_endpoints::<fblock::BlockMarker>();
462 std::thread::spawn(move || {
463 let mut executor = fasync::TestExecutor::new();
464 let _task =
465 executor.run_singlethreaded(server_clone.serve(block_server_end.into_stream()));
466 });
467
468 let tree = construct_fs_internal(
470 FsSourceType::BlockDevice(block_client_end),
471 false,
472 &fuchsia_inspect::Inspector::default(),
473 )
474 .expect("failed to parse the vmo");
475 let root = vfs::directory::serve(tree, fio::PERM_READABLE | fio::PERM_WRITABLE);
476 let file = open_file(&root, "file1", fio::PERM_READABLE | fio::PERM_WRITABLE)
477 .await
478 .expect("failed to open file");
479
480 let mut old_vmo_contents = vec![0u8; data.len()];
481 vmo_clone.read(&mut old_vmo_contents, 0).expect("failed to read from vmo clone");
482
483 let new_contents = "FILE1 CONTENTS!";
484 file.seek(fio::SeekOrigin::Start, 0)
485 .await
486 .expect("failed FIDL seek")
487 .map_err(zx::Status::from_raw)
488 .expect("failed to seek file");
489 write(&file, new_contents).await.expect("failed to write to file");
490
491 let mut vmo_contents_after_write = vec![0u8; data.len()];
492 vmo_clone.read(&mut vmo_contents_after_write, 0).expect("failed to read from vmo clone");
493
494 assert_eq!(
497 old_vmo_contents, vmo_contents_after_write,
498 "Data should be cached and not yet flushed to VmoBackedServer"
499 );
500
501 file.close()
503 .await
504 .expect("sync check failed")
505 .map_err(zx::Status::from_raw)
506 .expect("sync error");
507
508 let mut vmo_contents_after_sync = vec![0u8; data.len()];
509 vmo_clone.read(&mut vmo_contents_after_sync, 0).expect("failed to read from vmo clone");
510
511 assert_ne!(
513 old_vmo_contents, vmo_contents_after_sync,
514 "Data should be flushed to VmoBackedServer after sync"
515 );
516
517 root.close()
518 .await
519 .expect("failed FIDL dir close")
520 .map_err(zx::Status::from_raw)
521 .expect("failed to close root");
522 }
523
524 #[fuchsia::test]
525 async fn test_metrics_of_fs_with_multiple_files() {
526 let data = fs::read("/pkg/data/nest.img").expect("failed to read file");
527 let vmo = Vmo::create(data.len() as u64).expect("failed to create VMO");
528 vmo.write(data.as_slice(), 0).expect("failed to write to VMO");
529 let server = Arc::new(
530 VmoBackedServerOptions {
531 block_size: 512,
532 initial_contents: InitialContents::FromVmo(vmo),
533 ..Default::default()
534 }
535 .build()
536 .expect("build from VmoBackedServerOptions failed"),
537 );
538
539 let server_clone = server.clone();
540 let (block_client_end, block_server_end) =
541 fidl::endpoints::create_endpoints::<fblock::BlockMarker>();
542 std::thread::spawn(move || {
543 let mut executor = fasync::TestExecutor::new();
544 let _task =
545 executor.run_singlethreaded(server_clone.serve(block_server_end.into_stream()));
546 });
547
548 let inspector = fuchsia_inspect::Inspector::default();
549 let tree = construct_fs_internal(
550 FsSourceType::BlockDevice(block_client_end),
551 false,
552 &inspector,
553 )
554 .expect("failed to parse the vmo");
555 let root = vfs::directory::serve(tree, fio::PERM_READABLE | fio::PERM_WRITABLE);
556
557 let _status = open_file(
558 &root,
559 "file1",
560 fio::PERM_READABLE | fio::PERM_WRITABLE | fio::Flags::FILE_TRUNCATE,
561 )
562 .await
563 .expect_err("open with truncate should fail as it is currently not supported");
564 diagnostics_assertions::assert_data_tree!(inspector, root: {
565 file_metrics: {
566 num_open_requests: 1u64,
567 num_read_requests: 0u64,
568 num_truncate_requests: 1u64,
569 num_write_requests: 0u64,
570 num_writes_past_eof_attempts: 0u64,
571 num_successful_overwrites: 0u64,
572 num_blocks_overwritten: 0u64,
573 }
574 });
575
576 let file1 = open_file(&root, "file1", fio::PERM_READABLE | fio::PERM_WRITABLE)
578 .await
579 .expect("failed to open file1");
580 let _ = read_to_string(&file1).await.expect("failed to read file1");
581
582 let file2 = open_file(&root, "inner/file2", fio::PERM_READABLE | fio::PERM_WRITABLE)
583 .await
584 .expect("failed to open inner/file2");
585 let _ = read_to_string(&file2).await.expect("failed to read file2");
586
587 file1
588 .seek(fio::SeekOrigin::Start, 0)
589 .await
590 .expect("failed to seek")
591 .map_err(zx::Status::from_raw)
592 .expect("seek error");
593 let _ = write(&file1, "new content").await.expect("failed to write to file1");
594
595 diagnostics_assertions::assert_data_tree!(inspector, root: {
596 file_metrics: {
597 num_open_requests: 3u64,
598 num_read_requests: 2u64,
599 num_truncate_requests: 1u64,
600 num_write_requests: 1u64,
601 num_writes_past_eof_attempts: 0u64,
602 num_successful_overwrites: 1u64,
603 num_blocks_overwritten: 1u64,
604 }
605 });
606
607 root.close()
608 .await
609 .expect("failed FIDL dir close")
610 .map_err(zx::Status::from_raw)
611 .expect("failed to close root");
612 }
613}