Skip to main content

ext4_parser/
lib.rs

1// Copyright 2019 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use 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
40// Default is to create read-only fs
41pub fn construct_fs(
42    source: FsSourceType,
43    inspector: &fuchsia_inspect::Inspector,
44) -> Result<Arc<ExtDirectory>, ConstructFsError> {
45    // TODO(https://fxbug.dev/479943428): Enable creating writeable fs when this is fully supported.
46    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                // Too small to even fit the first copy of the ext4 Super Block.
65                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                // TODO(https://fxbug.dev/42073143): Handle other types.
113            }
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        // Create a device that is Ext4 formatted.
255        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        // Write to the allocated extent of this file.
278        let tree = construct_fs_internal(
279            FsSourceType::BlockDevice(block_client_end1),
280            /* read_only= */ 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        // Construct Ext4 fs again, and verify that the written data is still there.
310        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            /* read_only= */ 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        // Write to the allocated extent of this file.
371        let tree = construct_fs_internal(
372            FsSourceType::BlockDevice(block_client_end1),
373            /* read_only= */ 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        // There is not enough allocated bytes in this file to write this new content.
384        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        // Construct Ext4 fs again, and verify that the written data is still there.
410        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            /* read_only= */ 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        // Clone VMO to observe underlying changes made by the server.
447        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        // Write to the allocated extent of this file.
469        let tree = construct_fs_internal(
470            FsSourceType::BlockDevice(block_client_end),
471            /* read_only= */ 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        // The write is stored in the device cache but has not yet been been flushed to the
495        // underlying VMO.
496        assert_eq!(
497            old_vmo_contents, vmo_contents_after_write,
498            "Data should be cached and not yet flushed to VmoBackedServer"
499        );
500
501        // Closing the file will call sync to flush the contents to the backing VMO.
502        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        // Data should now be flushed to underlying VMO.
512        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            /* read_only= */ 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        // Perform opens, reads, and writes. Should see them reflected in the inspector metrics.
577        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}