1use 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
40pub fn construct_fs(source: FsSourceType) -> Result<Arc<ExtDirectory>, ConstructFsError> {
42 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 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 }
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 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 let tree = construct_fs_internal(
263 FsSourceType::BlockDevice(block_client_end1),
264 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 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 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 let tree = construct_fs_internal(
354 FsSourceType::BlockDevice(block_client_end1),
355 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 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 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 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 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 let tree = construct_fs_internal(
450 FsSourceType::BlockDevice(block_client_end),
451 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 assert_eq!(
476 old_vmo_contents, vmo_contents_after_write,
477 "Data should be cached and not yet flushed to VmoBackedServer"
478 );
479
480 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 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}