1use 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_hardware_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 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 dir.insert_child(entry_name, build_fs_file(parser, entry_ino)?)
84 .map_err(ConstructFsError::NodeError)?;
85 }
86 _ => {
87 }
89 }
90 }
91
92 Ok(dir)
93}
94
95fn build_fs_file(parser: &Parser, ino: u32) -> Result<Arc<ExtFile>, ConstructFsError> {
96 let inode = parser.inode(ino)?;
97 let attributes = ExtAttributes::from_inode(inode);
98 let xattrs = parser.inode_xattrs(ino)?;
99 let data = parser.read_data(ino)?;
100 let file = ExtFile::from_data(ino as u64, attributes, xattrs, data)
101 .map_err(ConstructFsError::NodeError)?;
102 Ok(file)
103}
104
105#[cfg(test)]
106mod tests {
107 use super::{FsSourceType, construct_fs};
108
109 use ext4_read_only::structs::MIN_EXT4_SIZE;
110 use fidl_fuchsia_io as fio;
111 use fuchsia_fs::directory::{DirEntry, DirentKind, open_file, open_node, readdir};
112 use fuchsia_fs::file::read_to_string;
113 use std::fs;
114 use zx::{Status, Vmo};
115
116 #[fuchsia::test]
117 fn image_too_small() {
118 let vmo = Vmo::create(10).expect("VMO is created");
119 vmo.write(b"too small", 0).expect("VMO write() succeeds");
120 let buffer = FsSourceType::Vmo(vmo);
121
122 assert!(construct_fs(buffer).is_err(), "Expected failed parsing of VMO.");
123 }
124
125 #[fuchsia::test]
126 fn invalid_fs() {
127 let vmo = Vmo::create(MIN_EXT4_SIZE as u64).expect("VMO is created");
128 vmo.write(b"not ext4", 0).expect("VMO write() succeeds");
129 let buffer = FsSourceType::Vmo(vmo);
130
131 assert!(construct_fs(buffer).is_err(), "Expected failed parsing of VMO.");
132 }
133
134 #[fuchsia::test]
135 async fn list_root() {
136 let data = fs::read("/pkg/data/nest.img").expect("Unable to read file");
137 let vmo = Vmo::create(data.len() as u64).expect("VMO is created");
138 vmo.write(data.as_slice(), 0).expect("VMO write() succeeds");
139 let buffer = FsSourceType::Vmo(vmo);
140
141 let tree = construct_fs(buffer).expect("construct_fs parses the vmo");
142 let root = vfs::directory::serve(tree, fio::PERM_READABLE);
143
144 let expected = vec![
145 DirEntry { name: String::from("file1"), kind: DirentKind::File },
146 DirEntry { name: String::from("inner"), kind: DirentKind::Directory },
147 DirEntry { name: String::from("lost+found"), kind: DirentKind::Directory },
148 ];
149 assert_eq!(readdir(&root).await.unwrap(), expected);
150
151 let file = open_file(&root, "file1", fio::PERM_READABLE).await.unwrap();
152 assert_eq!(read_to_string(&file).await.unwrap(), "file1 contents.\n");
153 file.close().await.unwrap().map_err(zx::Status::from_raw).unwrap();
154 root.close().await.unwrap().map_err(zx::Status::from_raw).unwrap();
155 }
156
157 #[fuchsia::test]
158 async fn get_dac_attributes() {
159 let data = fs::read("/pkg/data/dac_attributes.img").expect("Unable to read file");
160 let vmo = Vmo::create(data.len() as u64).expect("VMO is created");
161 vmo.write(data.as_slice(), 0).expect("VMO write() succeeds");
162 let buffer = FsSourceType::Vmo(vmo);
163
164 let tree = construct_fs(buffer).expect("construct_fs parses the VMO");
165 let root = vfs::directory::serve(tree, fio::PERM_READABLE);
166
167 let expected_entries = vec![
168 DirEntry { name: String::from("dir_1000"), kind: DirentKind::Directory },
169 DirEntry { name: String::from("dir_root"), kind: DirentKind::Directory },
170 DirEntry { name: String::from("file_1000"), kind: DirentKind::File },
171 DirEntry { name: String::from("file_root"), kind: DirentKind::File },
172 DirEntry { name: String::from("lost+found"), kind: DirentKind::Directory },
173 ];
174 assert_eq!(readdir(&root).await.unwrap(), expected_entries);
175
176 #[derive(Debug, PartialEq)]
177 struct Node {
178 name: String,
179 mode: u32,
180 uid: u32,
181 gid: u32,
182 }
183
184 let expected_attributes = vec![
185 Node { name: String::from("dir_1000"), mode: 0x416D, uid: 1000, gid: 1000 },
186 Node { name: String::from("dir_root"), mode: 0x4140, uid: 0, gid: 0 },
187 Node { name: String::from("file_1000"), mode: 0x8124, uid: 1000, gid: 1000 },
188 Node { name: String::from("file_root"), mode: 0x8100, uid: 0, gid: 0 },
189 ];
190
191 let attributes_query = fio::NodeAttributesQuery::MODE
192 | fio::NodeAttributesQuery::UID
193 | fio::NodeAttributesQuery::GID;
194 for expected_node in &expected_attributes {
195 let node_proxy = open_node(&root, expected_node.name.as_str(), fio::PERM_READABLE)
196 .await
197 .expect("node open failed");
198 let (mut_attrs, _immut_attrs) = node_proxy
199 .get_attributes(attributes_query)
200 .await
201 .expect("node get_attributes() failed")
202 .map_err(Status::from_raw)
203 .expect("node get_attributes() error");
204
205 let node = Node {
206 name: expected_node.name.clone(),
207 mode: mut_attrs.mode.expect("node attributes missing mode"),
208 uid: mut_attrs.uid.expect("node attributes missing uid"),
209 gid: mut_attrs.gid.expect("node attributes missing gid"),
210 };
211
212 node_proxy
213 .close()
214 .await
215 .expect("node close failed")
216 .map_err(Status::from_raw)
217 .expect("node close error");
218
219 assert_eq!(node, *expected_node);
220 }
221
222 root.close().await.unwrap().map_err(Status::from_raw).unwrap();
223 }
224}