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_read_only::processor::Ext4Processor;
9use ext4_read_only::readers::{BlockDeviceReader, ReaderWriter, VmoReader};
10use ext4_read_only::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(source: FsSourceType) -> Result<Arc<ExtDirectory>, ConstructFsError> {
42    // TODO(https://fxbug.dev/479943428): Enable creating writeable fs when this is fully supported.
43    construct_fs_internal(source, true)
44}
45
46fn construct_fs_internal(
47    source: FsSourceType,
48    read_only: bool,
49) -> Result<Arc<ExtDirectory>, ConstructFsError> {
50    let reader: Arc<dyn ReaderWriter> = match source {
51        FsSourceType::BlockDevice(block_device) => {
52            Arc::new(BlockDeviceReader::from_client_end(block_device).map_err(|e| {
53                error!("Error constructing file system: {}", e);
54                ConstructFsError::VmoReadError(zx::Status::IO_INVALID)
55            })?)
56        }
57        FsSourceType::Vmo(vmo) => {
58            let size = vmo.get_size().map_err(ConstructFsError::VmoReadError)?;
59            if size < MIN_EXT4_SIZE as u64 {
60                // Too small to even fit the first copy of the ext4 Super Block.
61                return Err(ConstructFsError::VmoReadError(zx::Status::NO_SPACE));
62            }
63
64            Arc::new(VmoReader::new(Arc::new(vmo)))
65        }
66    };
67    let processor = Ext4Processor::new(reader, read_only);
68    build_fs_dir(Arc::new(processor), structs::ROOT_INODE_NUM, read_only)
69}
70
71fn build_fs_dir(
72    processor: Arc<Ext4Processor>,
73    ino: u32,
74    read_only: bool,
75) -> Result<Arc<ExtDirectory>, ConstructFsError> {
76    let inode = processor.inode(ino)?;
77    let entries = processor.entries_from_inode(&inode)?;
78    let attributes = ExtAttributes::from_inode(inode);
79    let xattrs = processor.inode_xattrs(ino)?;
80    let dir = ExtDirectory::new(ino as u64, attributes, xattrs);
81
82    for entry in entries {
83        let entry_name = entry.name()?;
84        if entry_name == "." || entry_name == ".." {
85            continue;
86        }
87
88        let entry_ino = u32::from(entry.e2d_ino);
89        match EntryType::from_u8(entry.e2d_type)? {
90            EntryType::Directory => {
91                dir.insert_child(
92                    entry_name,
93                    build_fs_dir(processor.clone(), entry_ino, read_only)?,
94                )
95                .map_err(ConstructFsError::NodeError)?;
96            }
97            EntryType::RegularFile => {
98                dir.insert_child(
99                    entry_name,
100                    ExtFile::from_processor(processor.clone(), entry_ino, read_only)
101                        .map_err(ConstructFsError::NodeError)?,
102                )
103                .map_err(ConstructFsError::NodeError)?;
104            }
105            _ => {
106                // TODO(https://fxbug.dev/42073143): Handle other types.
107            }
108        }
109    }
110
111    Ok(dir)
112}
113
114#[cfg(test)]
115mod tests {
116    use super::{FsSourceType, construct_fs, construct_fs_internal};
117
118    use ext4_read_only::structs::MIN_EXT4_SIZE;
119    use fuchsia_fs::directory::{DirEntry, DirentKind, open_file, open_node, readdir};
120    use fuchsia_fs::file::{WriteError, read_to_string, write};
121    use std::fs;
122    use std::sync::Arc;
123    use vmo_backed_block_server::{InitialContents, VmoBackedServerOptions};
124    use zx::{HandleBased, Status, Vmo};
125    use {fidl_fuchsia_io as fio, fidl_fuchsia_storage_block as fblock, fuchsia_async as fasync};
126
127    #[fuchsia::test]
128    fn image_too_small() {
129        let vmo = Vmo::create(10).expect("VMO is created");
130        vmo.write(b"too small", 0).expect("VMO write() succeeds");
131        let buffer = FsSourceType::Vmo(vmo);
132
133        assert!(construct_fs(buffer).is_err(), "Expected failed parsing of VMO.");
134    }
135
136    #[fuchsia::test]
137    fn invalid_fs() {
138        let vmo = Vmo::create(MIN_EXT4_SIZE as u64).expect("VMO is created");
139        vmo.write(b"not ext4", 0).expect("VMO write() succeeds");
140        let buffer = FsSourceType::Vmo(vmo);
141
142        assert!(construct_fs(buffer).is_err(), "Expected failed parsing of VMO.");
143    }
144
145    #[fuchsia::test]
146    async fn list_root() {
147        let data = fs::read("/pkg/data/nest.img").expect("Unable to read file");
148        let vmo = Vmo::create(data.len() as u64).expect("VMO is created");
149        vmo.write(data.as_slice(), 0).expect("VMO write() succeeds");
150        let buffer = FsSourceType::Vmo(vmo);
151
152        let tree = construct_fs(buffer).expect("construct_fs parses the vmo");
153        let root = vfs::directory::serve(tree, fio::PERM_READABLE);
154
155        let expected = vec![
156            DirEntry { name: String::from("file1"), kind: DirentKind::File },
157            DirEntry { name: String::from("inner"), kind: DirentKind::Directory },
158            DirEntry { name: String::from("lost+found"), kind: DirentKind::Directory },
159        ];
160        assert_eq!(readdir(&root).await.unwrap(), expected);
161
162        let file = open_file(&root, "file1", fio::PERM_READABLE).await.unwrap();
163        assert_eq!(read_to_string(&file).await.unwrap(), "file1 contents.\n");
164        file.close().await.unwrap().map_err(zx::Status::from_raw).unwrap();
165        root.close().await.unwrap().map_err(zx::Status::from_raw).unwrap();
166    }
167
168    #[fuchsia::test]
169    async fn get_dac_attributes() {
170        let data = fs::read("/pkg/data/dac_attributes.img").expect("Unable to read file");
171        let vmo = Vmo::create(data.len() as u64).expect("VMO is created");
172        vmo.write(data.as_slice(), 0).expect("VMO write() succeeds");
173        let buffer = FsSourceType::Vmo(vmo);
174
175        let tree = construct_fs(buffer).expect("construct_fs parses the VMO");
176        let root = vfs::directory::serve(tree, fio::PERM_READABLE);
177
178        let expected_entries = vec![
179            DirEntry { name: String::from("dir_1000"), kind: DirentKind::Directory },
180            DirEntry { name: String::from("dir_root"), kind: DirentKind::Directory },
181            DirEntry { name: String::from("file_1000"), kind: DirentKind::File },
182            DirEntry { name: String::from("file_root"), kind: DirentKind::File },
183            DirEntry { name: String::from("lost+found"), kind: DirentKind::Directory },
184        ];
185        assert_eq!(readdir(&root).await.unwrap(), expected_entries);
186
187        #[derive(Debug, PartialEq)]
188        struct Node {
189            name: String,
190            mode: u32,
191            uid: u32,
192            gid: u32,
193        }
194
195        let expected_attributes = vec![
196            Node { name: String::from("dir_1000"), mode: 0x416D, uid: 1000, gid: 1000 },
197            Node { name: String::from("dir_root"), mode: 0x4140, uid: 0, gid: 0 },
198            Node { name: String::from("file_1000"), mode: 0x8124, uid: 1000, gid: 1000 },
199            Node { name: String::from("file_root"), mode: 0x8100, uid: 0, gid: 0 },
200        ];
201
202        let attributes_query = fio::NodeAttributesQuery::MODE
203            | fio::NodeAttributesQuery::UID
204            | fio::NodeAttributesQuery::GID;
205        for expected_node in &expected_attributes {
206            let node_proxy = open_node(&root, expected_node.name.as_str(), fio::PERM_READABLE)
207                .await
208                .expect("node open failed");
209            let (mut_attrs, _immut_attrs) = node_proxy
210                .get_attributes(attributes_query)
211                .await
212                .expect("node get_attributes() failed")
213                .map_err(Status::from_raw)
214                .expect("node get_attributes() error");
215
216            let node = Node {
217                name: expected_node.name.clone(),
218                mode: mut_attrs.mode.expect("node attributes missing mode"),
219                uid: mut_attrs.uid.expect("node attributes missing uid"),
220                gid: mut_attrs.gid.expect("node attributes missing gid"),
221            };
222
223            node_proxy
224                .close()
225                .await
226                .expect("node close failed")
227                .map_err(Status::from_raw)
228                .expect("node close error");
229
230            assert_eq!(node, *expected_node);
231        }
232
233        root.close().await.unwrap().map_err(Status::from_raw).unwrap();
234    }
235
236    #[fuchsia::test]
237    async fn test_constructing_writeable_fs_and_writing_to_allocated_region() {
238        // Create a device that is Ext4 formatted.
239        let data = fs::read("/pkg/data/nest.img").expect("failed to read file");
240        let vmo = Vmo::create(data.len() as u64).expect("failed to create VMO");
241        vmo.write(data.as_slice(), 0).expect("failed to write to VMO");
242        let server = Arc::new(
243            VmoBackedServerOptions {
244                block_size: 512,
245                initial_contents: InitialContents::FromVmo(vmo),
246                ..Default::default()
247            }
248            .build()
249            .expect("build from VmoBackedServerOptions failed"),
250        );
251
252        let server_clone = server.clone();
253        let (block_client_end1, block_server_end1) =
254            fidl::endpoints::create_endpoints::<fblock::BlockMarker>();
255        std::thread::spawn(move || {
256            let mut executor = fasync::TestExecutor::new();
257            let _task =
258                executor.run_singlethreaded(server_clone.serve(block_server_end1.into_stream()));
259        });
260
261        // Write to the allocated extent of this file.
262        let tree = construct_fs_internal(
263            FsSourceType::BlockDevice(block_client_end1),
264            /* read_only= */ false,
265        )
266        .expect("failed to parse the vmo");
267        let root = vfs::directory::serve(tree, fio::PERM_READABLE | fio::PERM_WRITABLE);
268        let file = open_file(&root, "file1", fio::PERM_READABLE | fio::PERM_WRITABLE)
269            .await
270            .expect("failed to open file");
271        let original_contents = "file1 contents.\n";
272        assert_eq!(read_to_string(&file).await.expect("failed to read file"), original_contents);
273        let new_contents = "new contents.";
274        let offset = 5;
275        file.seek(fio::SeekOrigin::Start, offset)
276            .await
277            .expect("failed FIDL seek")
278            .map_err(zx::Status::from_raw)
279            .expect("failed to seek file");
280        write(&file, new_contents).await.expect("failed to write to file");
281        file.close()
282            .await
283            .expect("failed FIDL file close")
284            .map_err(zx::Status::from_raw)
285            .expect("failed to close file");
286        root.close()
287            .await
288            .expect("failed FIDL dir close")
289            .map_err(zx::Status::from_raw)
290            .expect("failed to close root");
291
292        // Construct Ext4 fs again, and verify that the written data is still there.
293        let server_clone = server.clone();
294        let (block_client_end2, block_server_end2) =
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_end2.into_stream()));
300        });
301        let tree = construct_fs_internal(
302            FsSourceType::BlockDevice(block_client_end2),
303            /* read_only= */ true,
304        )
305        .expect("construct_fs parses the vmo");
306        let root = vfs::directory::serve(tree, fio::PERM_READABLE);
307        let file =
308            open_file(&root, "file1", fio::PERM_READABLE).await.expect("failed to open file");
309        let mut expected_bytes = original_contents.as_bytes().to_vec();
310        expected_bytes.resize(offset as usize + new_contents.len(), 0);
311        expected_bytes[offset as usize..].copy_from_slice(new_contents.as_bytes());
312        assert_eq!(
313            read_to_string(&file).await.expect("failed to read file"),
314            String::from_utf8(expected_bytes).unwrap()
315        );
316        file.close()
317            .await
318            .expect("failed FIDL file close")
319            .map_err(zx::Status::from_raw)
320            .expect("failed to close file");
321        root.close()
322            .await
323            .expect("failed FIDL dir close")
324            .map_err(zx::Status::from_raw)
325            .expect("failed to close root");
326    }
327
328    #[fuchsia::test]
329    async fn test_writing_to_unallocated_region_fails() {
330        let data = fs::read("/pkg/data/nest.img").expect("failed to read file");
331        let vmo = Vmo::create(data.len() as u64).expect("failed to create VMO");
332        vmo.write(data.as_slice(), 0).expect("failed to write to VMO");
333        let server = Arc::new(
334            VmoBackedServerOptions {
335                block_size: 512,
336                initial_contents: InitialContents::FromVmo(vmo),
337                ..Default::default()
338            }
339            .build()
340            .expect("build from VmoBackedServerOptions failed"),
341        );
342
343        let server_clone = server.clone();
344        let (block_client_end1, block_server_end1) =
345            fidl::endpoints::create_endpoints::<fblock::BlockMarker>();
346        std::thread::spawn(move || {
347            let mut executor = fasync::TestExecutor::new();
348            let _task =
349                executor.run_singlethreaded(server_clone.serve(block_server_end1.into_stream()));
350        });
351
352        // Write to the allocated extent of this file.
353        let tree = construct_fs_internal(
354            FsSourceType::BlockDevice(block_client_end1),
355            /* read_only= */ false,
356        )
357        .expect("failed to parse the vmo");
358        let root = vfs::directory::serve(tree, fio::PERM_READABLE | fio::PERM_WRITABLE);
359        let file = open_file(&root, "file1", fio::PERM_READABLE | fio::PERM_WRITABLE)
360            .await
361            .expect("failed to open file");
362        let original_contents = read_to_string(&file).await.expect("failed to read file");
363
364        // There is not enough allocated bytes in this file to write this new content.
365        let new_contents = [1u8; 8192];
366        file.seek(fio::SeekOrigin::Start, 0)
367            .await
368            .expect("failed FIDL seek")
369            .map_err(zx::Status::from_raw)
370            .expect("failed to seek file");
371        let error = write(&file, &new_contents)
372            .await
373            .expect_err("write to unallocated region passed unexpectedly");
374        match error {
375            WriteError::WriteError(status) => assert_eq!(status, zx::Status::NOT_SUPPORTED),
376            _ => panic!("Unexpected error: {:?}", error),
377        }
378
379        file.close()
380            .await
381            .expect("failed FIDL file close")
382            .map_err(zx::Status::from_raw)
383            .expect("failed to close file");
384        root.close()
385            .await
386            .expect("failed FIDL dir close")
387            .map_err(zx::Status::from_raw)
388            .expect("failed to close root");
389
390        // Construct Ext4 fs again, and verify that the written data is still there.
391        let server_clone = server.clone();
392        let (block_client_end2, block_server_end2) =
393            fidl::endpoints::create_endpoints::<fblock::BlockMarker>();
394        std::thread::spawn(move || {
395            let mut executor = fasync::TestExecutor::new();
396            let _task =
397                executor.run_singlethreaded(server_clone.serve(block_server_end2.into_stream()));
398        });
399        let tree = construct_fs_internal(
400            FsSourceType::BlockDevice(block_client_end2),
401            /* read_only= */ true,
402        )
403        .expect("construct_fs parses the vmo");
404        let root = vfs::directory::serve(tree, fio::PERM_READABLE);
405        let file =
406            open_file(&root, "file1", fio::PERM_READABLE).await.expect("failed to open file");
407        assert_eq!(read_to_string(&file).await.expect("failed to read file"), original_contents);
408        file.close()
409            .await
410            .expect("failed FIDL file close")
411            .map_err(zx::Status::from_raw)
412            .expect("failed to close file");
413        root.close()
414            .await
415            .expect("failed FIDL dir close")
416            .map_err(zx::Status::from_raw)
417            .expect("failed to close root");
418    }
419
420    #[fuchsia::test]
421    async fn test_file_sync() {
422        let data = fs::read("/pkg/data/nest.img").expect("failed to read file");
423        let vmo = Vmo::create(data.len() as u64).expect("failed to create VMO");
424        vmo.write(data.as_slice(), 0).expect("failed to write to VMO");
425
426        // Clone VMO to observe underlying changes made by the server.
427        let vmo_clone = vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).expect("failed to clone vmo");
428
429        let server = Arc::new(
430            VmoBackedServerOptions {
431                block_size: 512,
432                initial_contents: InitialContents::FromVmo(vmo),
433                ..Default::default()
434            }
435            .build()
436            .expect("build from VmoBackedServerOptions failed"),
437        );
438
439        let server_clone = server.clone();
440        let (block_client_end, block_server_end) =
441            fidl::endpoints::create_endpoints::<fblock::BlockMarker>();
442        std::thread::spawn(move || {
443            let mut executor = fasync::TestExecutor::new();
444            let _task =
445                executor.run_singlethreaded(server_clone.serve(block_server_end.into_stream()));
446        });
447
448        // Write to the allocated extent of this file.
449        let tree = construct_fs_internal(
450            FsSourceType::BlockDevice(block_client_end),
451            /* read_only= */ false,
452        )
453        .expect("failed to parse the vmo");
454        let root = vfs::directory::serve(tree, fio::PERM_READABLE | fio::PERM_WRITABLE);
455        let file = open_file(&root, "file1", fio::PERM_READABLE | fio::PERM_WRITABLE)
456            .await
457            .expect("failed to open file");
458
459        let mut old_vmo_contents = vec![0u8; data.len()];
460        vmo_clone.read(&mut old_vmo_contents, 0).expect("failed to read from vmo clone");
461
462        let new_contents = "new cached contents!";
463        file.seek(fio::SeekOrigin::Start, 0)
464            .await
465            .expect("failed FIDL seek")
466            .map_err(zx::Status::from_raw)
467            .expect("failed to seek file");
468        write(&file, new_contents).await.expect("failed to write to file");
469
470        let mut vmo_contents_after_write = vec![0u8; data.len()];
471        vmo_clone.read(&mut vmo_contents_after_write, 0).expect("failed to read from vmo clone");
472
473        // The write is stored in the device cache but has not yet been been flushed to the
474        // underlying VMO.
475        assert_eq!(
476            old_vmo_contents, vmo_contents_after_write,
477            "Data should be cached and not yet flushed to VmoBackedServer"
478        );
479
480        // Closing the file will call sync to flush the contents to the backing VMO.
481        file.close()
482            .await
483            .expect("sync check failed")
484            .map_err(zx::Status::from_raw)
485            .expect("sync error");
486
487        let mut vmo_contents_after_sync = vec![0u8; data.len()];
488        vmo_clone.read(&mut vmo_contents_after_sync, 0).expect("failed to read from vmo clone");
489
490        // Data should now be flushed to underlying VMO.
491        assert_ne!(
492            old_vmo_contents, vmo_contents_after_sync,
493            "Data should be flushed to VmoBackedServer after sync"
494        );
495
496        root.close()
497            .await
498            .expect("failed FIDL dir close")
499            .map_err(zx::Status::from_raw)
500            .expect("failed to close root");
501    }
502}