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::parser::Parser;
9use ext4_read_only::readers::{BlockDeviceReader, Reader, 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
40pub fn construct_fs(source: FsSourceType) -> Result<Arc<ExtDirectory>, ConstructFsError> {
41    let reader: Box<dyn Reader> = match source {
42        FsSourceType::BlockDevice(block_device) => {
43            Box::new(BlockDeviceReader::from_client_end(block_device).map_err(|e| {
44                error!("Error constructing file system: {}", e);
45                ConstructFsError::VmoReadError(zx::Status::IO_INVALID)
46            })?)
47        }
48        FsSourceType::Vmo(vmo) => {
49            let size = vmo.get_size().map_err(ConstructFsError::VmoReadError)?;
50            if size < MIN_EXT4_SIZE as u64 {
51                // Too small to even fit the first copy of the ext4 Super Block.
52                return Err(ConstructFsError::VmoReadError(zx::Status::NO_SPACE));
53            }
54
55            Box::new(VmoReader::new(Arc::new(vmo)))
56        }
57    };
58
59    let parser = Parser::new(reader);
60    build_fs_dir(&parser, structs::ROOT_INODE_NUM)
61}
62
63fn build_fs_dir(parser: &Parser, ino: u32) -> Result<Arc<ExtDirectory>, ConstructFsError> {
64    let inode = parser.inode(ino)?;
65    let entries = parser.entries_from_inode(&inode)?;
66    let attributes = ExtAttributes::from_inode(inode);
67    let xattrs = parser.inode_xattrs(ino)?;
68    let dir = ExtDirectory::new(ino as u64, attributes, xattrs);
69
70    for entry in entries {
71        let entry_name = entry.name()?;
72        if entry_name == "." || entry_name == ".." {
73            continue;
74        }
75
76        let entry_ino = u32::from(entry.e2d_ino);
77        match EntryType::from_u8(entry.e2d_type)? {
78            EntryType::Directory => {
79                dir.insert_child(entry_name, build_fs_dir(parser, entry_ino)?)
80                    .map_err(ConstructFsError::NodeError)?;
81            }
82            EntryType::RegularFile => {
83                // TODO(https://fxbug.dev/479943428): Enable creating writeable file when this is
84                // properly supported.
85                dir.insert_child(entry_name, build_fs_file(parser, entry_ino, false)?)
86                    .map_err(ConstructFsError::NodeError)?;
87            }
88            _ => {
89                // TODO(https://fxbug.dev/42073143): Handle other types.
90            }
91        }
92    }
93
94    Ok(dir)
95}
96
97fn build_fs_file(
98    parser: &Parser,
99    ino: u32,
100    writeable: bool,
101) -> Result<Arc<ExtFile>, ConstructFsError> {
102    let inode = parser.inode(ino)?;
103    let attributes = ExtAttributes::from_inode(inode);
104    let xattrs = parser.inode_xattrs(ino)?;
105    let data = parser.read_data(ino)?;
106    let file = ExtFile::from_data(ino as u64, attributes, xattrs, data, writeable)
107        .map_err(ConstructFsError::NodeError)?;
108    Ok(file)
109}
110
111#[cfg(test)]
112mod tests {
113    use super::{FsSourceType, construct_fs};
114
115    use ext4_read_only::structs::MIN_EXT4_SIZE;
116    use fidl_fuchsia_io as fio;
117    use fuchsia_fs::directory::{DirEntry, DirentKind, open_file, open_node, readdir};
118    use fuchsia_fs::file::read_to_string;
119    use std::fs;
120    use zx::{Status, Vmo};
121
122    #[fuchsia::test]
123    fn image_too_small() {
124        let vmo = Vmo::create(10).expect("VMO is created");
125        vmo.write(b"too small", 0).expect("VMO write() succeeds");
126        let buffer = FsSourceType::Vmo(vmo);
127
128        assert!(construct_fs(buffer).is_err(), "Expected failed parsing of VMO.");
129    }
130
131    #[fuchsia::test]
132    fn invalid_fs() {
133        let vmo = Vmo::create(MIN_EXT4_SIZE as u64).expect("VMO is created");
134        vmo.write(b"not ext4", 0).expect("VMO write() succeeds");
135        let buffer = FsSourceType::Vmo(vmo);
136
137        assert!(construct_fs(buffer).is_err(), "Expected failed parsing of VMO.");
138    }
139
140    #[fuchsia::test]
141    async fn list_root() {
142        let data = fs::read("/pkg/data/nest.img").expect("Unable to read file");
143        let vmo = Vmo::create(data.len() as u64).expect("VMO is created");
144        vmo.write(data.as_slice(), 0).expect("VMO write() succeeds");
145        let buffer = FsSourceType::Vmo(vmo);
146
147        let tree = construct_fs(buffer).expect("construct_fs parses the vmo");
148        let root = vfs::directory::serve(tree, fio::PERM_READABLE);
149
150        let expected = vec![
151            DirEntry { name: String::from("file1"), kind: DirentKind::File },
152            DirEntry { name: String::from("inner"), kind: DirentKind::Directory },
153            DirEntry { name: String::from("lost+found"), kind: DirentKind::Directory },
154        ];
155        assert_eq!(readdir(&root).await.unwrap(), expected);
156
157        let file = open_file(&root, "file1", fio::PERM_READABLE).await.unwrap();
158        assert_eq!(read_to_string(&file).await.unwrap(), "file1 contents.\n");
159        file.close().await.unwrap().map_err(zx::Status::from_raw).unwrap();
160        root.close().await.unwrap().map_err(zx::Status::from_raw).unwrap();
161    }
162
163    #[fuchsia::test]
164    async fn get_dac_attributes() {
165        let data = fs::read("/pkg/data/dac_attributes.img").expect("Unable to read file");
166        let vmo = Vmo::create(data.len() as u64).expect("VMO is created");
167        vmo.write(data.as_slice(), 0).expect("VMO write() succeeds");
168        let buffer = FsSourceType::Vmo(vmo);
169
170        let tree = construct_fs(buffer).expect("construct_fs parses the VMO");
171        let root = vfs::directory::serve(tree, fio::PERM_READABLE);
172
173        let expected_entries = vec![
174            DirEntry { name: String::from("dir_1000"), kind: DirentKind::Directory },
175            DirEntry { name: String::from("dir_root"), kind: DirentKind::Directory },
176            DirEntry { name: String::from("file_1000"), kind: DirentKind::File },
177            DirEntry { name: String::from("file_root"), kind: DirentKind::File },
178            DirEntry { name: String::from("lost+found"), kind: DirentKind::Directory },
179        ];
180        assert_eq!(readdir(&root).await.unwrap(), expected_entries);
181
182        #[derive(Debug, PartialEq)]
183        struct Node {
184            name: String,
185            mode: u32,
186            uid: u32,
187            gid: u32,
188        }
189
190        let expected_attributes = vec![
191            Node { name: String::from("dir_1000"), mode: 0x416D, uid: 1000, gid: 1000 },
192            Node { name: String::from("dir_root"), mode: 0x4140, uid: 0, gid: 0 },
193            Node { name: String::from("file_1000"), mode: 0x8124, uid: 1000, gid: 1000 },
194            Node { name: String::from("file_root"), mode: 0x8100, uid: 0, gid: 0 },
195        ];
196
197        let attributes_query = fio::NodeAttributesQuery::MODE
198            | fio::NodeAttributesQuery::UID
199            | fio::NodeAttributesQuery::GID;
200        for expected_node in &expected_attributes {
201            let node_proxy = open_node(&root, expected_node.name.as_str(), fio::PERM_READABLE)
202                .await
203                .expect("node open failed");
204            let (mut_attrs, _immut_attrs) = node_proxy
205                .get_attributes(attributes_query)
206                .await
207                .expect("node get_attributes() failed")
208                .map_err(Status::from_raw)
209                .expect("node get_attributes() error");
210
211            let node = Node {
212                name: expected_node.name.clone(),
213                mode: mut_attrs.mode.expect("node attributes missing mode"),
214                uid: mut_attrs.uid.expect("node attributes missing uid"),
215                gid: mut_attrs.gid.expect("node attributes missing gid"),
216            };
217
218            node_proxy
219                .close()
220                .await
221                .expect("node close failed")
222                .map_err(Status::from_raw)
223                .expect("node close error");
224
225            assert_eq!(node, *expected_node);
226        }
227
228        root.close().await.unwrap().map_err(Status::from_raw).unwrap();
229    }
230}