1use crate::directory::ExtDirectory;
6use crate::file::ExtFile;
7use crate::symlink::ExtSymlink;
8use crate::types::ExtAttributes;
9use ext4_lib::processor::Ext4Processor;
10use ext4_lib::readers::{BlockDeviceReader, ReaderWriter, VmoReader};
11use ext4_lib::structs::{self, EntryType, MIN_EXT4_SIZE};
12use fidl::endpoints::ClientEnd;
13use fidl_fuchsia_storage_block::BlockMarker;
14use log::error;
15use std::sync::Arc;
16
17mod directory;
18mod file;
19mod node;
20mod symlink;
21mod types;
22
23pub enum FsSourceType {
24 BlockDevice(ClientEnd<BlockMarker>),
25 Vmo(zx::Vmo),
26}
27
28#[derive(Debug, PartialEq)]
29pub enum ConstructFsError {
30 VmoReadError(zx::Status),
31 ParsingError(structs::ParsingError),
32 FileVmoError(zx::Status),
33 NodeError(zx::Status),
34}
35
36impl From<structs::ParsingError> for ConstructFsError {
37 fn from(value: structs::ParsingError) -> Self {
38 Self::ParsingError(value)
39 }
40}
41
42pub fn construct_fs(
43 source: FsSourceType,
44 read_only: bool,
45 inspector: &fuchsia_inspect::Inspector,
46) -> Result<Arc<ExtDirectory>, ConstructFsError> {
47 let reader: Arc<dyn ReaderWriter> = match source {
48 FsSourceType::BlockDevice(block_device) => {
49 Arc::new(BlockDeviceReader::from_client_end(block_device).map_err(|e| {
50 error!("Error constructing file system: {}", e);
51 ConstructFsError::VmoReadError(zx::Status::IO_INVALID)
52 })?)
53 }
54 FsSourceType::Vmo(vmo) => {
55 let size = vmo.get_size().map_err(ConstructFsError::VmoReadError)?;
56 if size < MIN_EXT4_SIZE as u64 {
57 return Err(ConstructFsError::VmoReadError(zx::Status::NO_SPACE));
59 }
60
61 Arc::new(VmoReader::new(Arc::new(vmo)))
62 }
63 };
64 let processor = Arc::new(Ext4Processor::new(reader, read_only));
65 let dir = build_fs_dir(processor.clone(), structs::ROOT_INODE_NUM, read_only)?;
66 processor.record_statistics(inspector.root());
67 Ok(dir)
68}
69
70fn build_fs_dir(
71 processor: Arc<Ext4Processor>,
72 ino: u32,
73 read_only: bool,
74) -> Result<Arc<ExtDirectory>, ConstructFsError> {
75 let inode = processor.inode(ino)?;
76 let entries = processor.entries_from_inode(&inode)?;
77 let attributes = ExtAttributes::from_inode(inode);
78 let xattrs = processor.inode_xattrs(ino)?;
79 let dir = ExtDirectory::new(ino as u64, attributes, xattrs);
80
81 for entry in entries {
82 let entry_name = entry.name()?;
83 if entry_name == "." || entry_name == ".." {
84 continue;
85 }
86
87 let entry_ino = u32::from(entry.e2d_ino);
88 match EntryType::from_u8(entry.e2d_type)? {
89 EntryType::Directory => {
90 dir.insert_child(
91 entry_name,
92 build_fs_dir(processor.clone(), entry_ino, read_only)?,
93 )
94 .map_err(ConstructFsError::NodeError)?;
95 }
96 EntryType::RegularFile => {
97 dir.insert_child(
98 entry_name,
99 ExtFile::from_processor(processor.clone(), entry_ino, read_only)
100 .map_err(ConstructFsError::NodeError)?,
101 )
102 .map_err(ConstructFsError::NodeError)?;
103 }
104 EntryType::SymLink => {
105 dir.insert_child(
106 entry_name,
107 ExtSymlink::from_processor(processor.clone(), entry_ino)
108 .map_err(ConstructFsError::NodeError)?,
109 )
110 .map_err(ConstructFsError::NodeError)?;
111 }
112 _ => {
113 }
115 }
116 }
117
118 Ok(dir)
119}
120
121#[cfg(test)]
122mod tests {
123 use super::{ExtAttributes, ExtDirectory, ExtSymlink, FsSourceType, construct_fs};
124
125 use ext4_lib::parser::XattrMap;
126 use ext4_lib::structs::MIN_EXT4_SIZE;
127 use fidl_fuchsia_io as fio;
128 use fidl_fuchsia_storage_block as fblock;
129 use fuchsia_async as fasync;
130 use fuchsia_fs::directory::{DirEntry, DirentKind, open_file, open_node, readdir};
131 use fuchsia_fs::file::{WriteError, read_to_string, write};
132 use futures::stream::StreamExt as _;
133 use std::fs;
134 use std::sync::Arc;
135 use test_vmo_backed_block_server::VmoBackedServer;
136 use zx::{Status, Vmo};
137
138 const BLOCK_SIZE: u32 = 512;
139
140 #[fuchsia::test]
141 fn image_too_small() {
142 let vmo = Vmo::create(10).expect("VMO is created");
143 vmo.write(b"too small", 0).expect("VMO write() succeeds");
144 let buffer = FsSourceType::Vmo(vmo);
145
146 assert!(
147 construct_fs(buffer, true, &fuchsia_inspect::Inspector::default()).is_err(),
148 "Expected failed parsing of VMO."
149 );
150 }
151
152 #[fuchsia::test]
153 fn invalid_fs() {
154 let vmo = Vmo::create(MIN_EXT4_SIZE as u64).expect("VMO is created");
155 vmo.write(b"not ext4", 0).expect("VMO write() succeeds");
156 let buffer = FsSourceType::Vmo(vmo);
157
158 assert!(
159 construct_fs(buffer, true, &fuchsia_inspect::Inspector::default()).is_err(),
160 "Expected failed parsing of VMO."
161 );
162 }
163
164 #[fuchsia::test]
165 async fn list_root() {
166 let data = fs::read("/pkg/data/nest.img").expect("Unable to read file");
167 let vmo = Vmo::create(data.len() as u64).expect("VMO is created");
168 vmo.write(data.as_slice(), 0).expect("VMO write() succeeds");
169 let buffer = FsSourceType::Vmo(vmo);
170
171 let tree = construct_fs(buffer, true, &fuchsia_inspect::Inspector::default())
172 .expect("construct_fs parses the vmo");
173 let root = vfs::directory::serve(
174 tree,
175 vfs::execution_scope::ExecutionScope::new(),
176 fio::PERM_READABLE,
177 );
178
179 let expected = vec![
180 DirEntry { name: String::from("file1"), kind: DirentKind::File },
181 DirEntry { name: String::from("inner"), kind: DirentKind::Directory },
182 DirEntry { name: String::from("lost+found"), kind: DirentKind::Directory },
183 ];
184 assert_eq!(readdir(&root).await.unwrap(), expected);
185
186 let file = open_file(&root, "file1", fio::PERM_READABLE).await.unwrap();
187 assert_eq!(read_to_string(&file).await.unwrap(), "file1 contents.\n");
188 file.close().await.unwrap().map_err(zx::Status::from_raw).unwrap();
189 root.close().await.unwrap().map_err(zx::Status::from_raw).unwrap();
190 }
191
192 #[fuchsia::test]
193 async fn get_dac_attributes() {
194 let data = fs::read("/pkg/data/dac_attributes.img").expect("Unable to read file");
195 let vmo = Vmo::create(data.len() as u64).expect("VMO is created");
196 vmo.write(data.as_slice(), 0).expect("VMO write() succeeds");
197 let buffer = FsSourceType::Vmo(vmo);
198
199 let tree = construct_fs(buffer, true, &fuchsia_inspect::Inspector::default())
200 .expect("construct_fs parses the VMO");
201 let root = vfs::directory::serve(
202 tree,
203 vfs::execution_scope::ExecutionScope::new(),
204 fio::PERM_READABLE,
205 );
206
207 let expected_entries = vec![
208 DirEntry { name: String::from("dir_1000"), kind: DirentKind::Directory },
209 DirEntry { name: String::from("dir_root"), kind: DirentKind::Directory },
210 DirEntry { name: String::from("file_1000"), kind: DirentKind::File },
211 DirEntry { name: String::from("file_root"), kind: DirentKind::File },
212 DirEntry { name: String::from("lost+found"), kind: DirentKind::Directory },
213 ];
214 assert_eq!(readdir(&root).await.unwrap(), expected_entries);
215
216 #[derive(Debug, PartialEq)]
217 struct Node {
218 name: String,
219 mode: u32,
220 uid: u32,
221 gid: u32,
222 }
223
224 let expected_attributes = vec![
225 Node { name: String::from("dir_1000"), mode: 0x416D, uid: 1000, gid: 1000 },
226 Node { name: String::from("dir_root"), mode: 0x4140, uid: 0, gid: 0 },
227 Node { name: String::from("file_1000"), mode: 0x8124, uid: 1000, gid: 1000 },
228 Node { name: String::from("file_root"), mode: 0x8100, uid: 0, gid: 0 },
229 ];
230
231 let attributes_query = fio::NodeAttributesQuery::MODE
232 | fio::NodeAttributesQuery::UID
233 | fio::NodeAttributesQuery::GID;
234 for expected_node in &expected_attributes {
235 let node_proxy = open_node(&root, expected_node.name.as_str(), fio::PERM_READABLE)
236 .await
237 .expect("node open failed");
238 let (mut_attrs, _immut_attrs) = node_proxy
239 .get_attributes(attributes_query)
240 .await
241 .expect("node get_attributes() failed")
242 .map_err(Status::from_raw)
243 .expect("node get_attributes() error");
244
245 let node = Node {
246 name: expected_node.name.clone(),
247 mode: mut_attrs.mode.expect("node attributes missing mode"),
248 uid: mut_attrs.uid.expect("node attributes missing uid"),
249 gid: mut_attrs.gid.expect("node attributes missing gid"),
250 };
251
252 node_proxy
253 .close()
254 .await
255 .expect("node close failed")
256 .map_err(Status::from_raw)
257 .expect("node close error");
258
259 assert_eq!(node, *expected_node);
260 }
261
262 root.close().await.unwrap().map_err(Status::from_raw).unwrap();
263 }
264
265 #[fuchsia::test]
266 async fn test_constructing_writeable_fs_and_writing_to_allocated_region() {
267 let server = Arc::new(VmoBackedServer::from_file(BLOCK_SIZE, "/pkg/data/nest.img"));
269
270 let server_clone = server.clone();
271 let (block_client_end1, block_server_end1) =
272 fidl::endpoints::create_endpoints::<fblock::BlockMarker>();
273 std::thread::spawn(move || {
274 let mut executor = fasync::TestExecutor::new();
275 let _task =
276 executor.run_singlethreaded(server_clone.serve(block_server_end1.into_stream()));
277 });
278
279 let tree = construct_fs(
281 FsSourceType::BlockDevice(block_client_end1),
282 false,
283 &fuchsia_inspect::Inspector::default(),
284 )
285 .expect("failed to parse the vmo");
286 let root = vfs::directory::serve(
287 tree,
288 vfs::execution_scope::ExecutionScope::new(),
289 fio::PERM_READABLE | fio::PERM_WRITABLE,
290 );
291 let file = open_file(&root, "file1", fio::PERM_READABLE | fio::PERM_WRITABLE)
292 .await
293 .expect("failed to open file");
294 let original_contents = "file1 contents.\n";
295 assert_eq!(read_to_string(&file).await.expect("failed to read file"), original_contents);
296 let new_contents = "new";
297 let offset = 5;
298 file.seek(fio::SeekOrigin::Start, offset)
299 .await
300 .expect("failed FIDL seek")
301 .map_err(zx::Status::from_raw)
302 .expect("failed to seek file");
303 write(&file, new_contents).await.expect("failed to write to file");
304 file.close()
305 .await
306 .expect("failed FIDL file close")
307 .map_err(zx::Status::from_raw)
308 .expect("failed to close file");
309 root.close()
310 .await
311 .expect("failed FIDL dir close")
312 .map_err(zx::Status::from_raw)
313 .expect("failed to close root");
314
315 let server_clone = server.clone();
317 let (block_client_end2, block_server_end2) =
318 fidl::endpoints::create_endpoints::<fblock::BlockMarker>();
319 std::thread::spawn(move || {
320 let mut executor = fasync::TestExecutor::new();
321 let _task =
322 executor.run_singlethreaded(server_clone.serve(block_server_end2.into_stream()));
323 });
324 let tree = construct_fs(
325 FsSourceType::BlockDevice(block_client_end2),
326 true,
327 &fuchsia_inspect::Inspector::default(),
328 )
329 .expect("construct_fs parses the vmo");
330 let root = vfs::directory::serve(
331 tree,
332 vfs::execution_scope::ExecutionScope::new(),
333 fio::PERM_READABLE,
334 );
335 let file =
336 open_file(&root, "file1", fio::PERM_READABLE).await.expect("failed to open file");
337 let mut expected_bytes = original_contents.as_bytes().to_vec();
338 expected_bytes[offset as usize..offset as usize + new_contents.len()]
339 .copy_from_slice(new_contents.as_bytes());
340 assert_eq!(
341 read_to_string(&file).await.expect("failed to read file"),
342 String::from_utf8(expected_bytes).unwrap()
343 );
344 file.close()
345 .await
346 .expect("failed FIDL file close")
347 .map_err(zx::Status::from_raw)
348 .expect("failed to close file");
349 root.close()
350 .await
351 .expect("failed FIDL dir close")
352 .map_err(zx::Status::from_raw)
353 .expect("failed to close root");
354 }
355
356 #[fuchsia::test]
357 async fn test_writing_past_eof_fails() {
358 let server = Arc::new(VmoBackedServer::from_file(BLOCK_SIZE, "/pkg/data/nest.img"));
359
360 let server_clone = server.clone();
361 let (block_client_end1, block_server_end1) =
362 fidl::endpoints::create_endpoints::<fblock::BlockMarker>();
363 std::thread::spawn(move || {
364 let mut executor = fasync::TestExecutor::new();
365 let _task =
366 executor.run_singlethreaded(server_clone.serve(block_server_end1.into_stream()));
367 });
368
369 let tree = construct_fs(
371 FsSourceType::BlockDevice(block_client_end1),
372 false,
373 &fuchsia_inspect::Inspector::default(),
374 )
375 .expect("failed to parse the vmo");
376 let root = vfs::directory::serve(
377 tree,
378 vfs::execution_scope::ExecutionScope::new(),
379 fio::PERM_READABLE | fio::PERM_WRITABLE,
380 );
381 let file = open_file(&root, "file1", fio::PERM_READABLE | fio::PERM_WRITABLE)
382 .await
383 .expect("failed to open file");
384 let original_contents = read_to_string(&file).await.expect("failed to read file");
385
386 let new_contents = [1u8; 8192];
388 file.seek(fio::SeekOrigin::Start, 0)
389 .await
390 .expect("failed FIDL seek")
391 .map_err(zx::Status::from_raw)
392 .expect("failed to seek file");
393 let error = write(&file, &new_contents)
394 .await
395 .expect_err("write to unallocated region passed unexpectedly");
396 match error {
397 WriteError::WriteError(status) => assert_eq!(status, zx::Status::NOT_SUPPORTED),
398 _ => panic!("Unexpected error: {:?}", error),
399 }
400
401 file.close()
402 .await
403 .expect("failed FIDL file close")
404 .map_err(zx::Status::from_raw)
405 .expect("failed to close file");
406 root.close()
407 .await
408 .expect("failed FIDL dir close")
409 .map_err(zx::Status::from_raw)
410 .expect("failed to close root");
411
412 let server_clone = server.clone();
414 let (block_client_end2, block_server_end2) =
415 fidl::endpoints::create_endpoints::<fblock::BlockMarker>();
416 std::thread::spawn(move || {
417 let mut executor = fasync::TestExecutor::new();
418 let _task =
419 executor.run_singlethreaded(server_clone.serve(block_server_end2.into_stream()));
420 });
421 let tree = construct_fs(
422 FsSourceType::BlockDevice(block_client_end2),
423 true,
424 &fuchsia_inspect::Inspector::default(),
425 )
426 .expect("construct_fs parses the vmo");
427 let root = vfs::directory::serve(
428 tree,
429 vfs::execution_scope::ExecutionScope::new(),
430 fio::PERM_READABLE,
431 );
432 let file =
433 open_file(&root, "file1", fio::PERM_READABLE).await.expect("failed to open file");
434 assert_eq!(read_to_string(&file).await.expect("failed to read file"), original_contents);
435 file.close()
436 .await
437 .expect("failed FIDL file close")
438 .map_err(zx::Status::from_raw)
439 .expect("failed to close file");
440 root.close()
441 .await
442 .expect("failed FIDL dir close")
443 .map_err(zx::Status::from_raw)
444 .expect("failed to close root");
445 }
446
447 #[fuchsia::test]
448 async fn test_file_sync() {
449 let data = fs::read("/pkg/data/nest.img").expect("failed to read file");
450 let vmo = Vmo::create(data.len() as u64).expect("failed to create VMO");
451 vmo.write(data.as_slice(), 0).expect("failed to write to VMO");
452
453 let vmo_clone = vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).expect("failed to clone vmo");
455
456 let server =
457 VmoBackedServer::from_vmo(BLOCK_SIZE, vmo).expect("Failed to create VmoBackedServer");
458
459 let (block_client_end, block_server_end) =
460 fidl::endpoints::create_endpoints::<fblock::BlockMarker>();
461 std::thread::spawn(move || {
462 let mut executor = fasync::TestExecutor::new();
463 let _task = executor.run_singlethreaded(server.serve(block_server_end.into_stream()));
464 });
465
466 let tree = construct_fs(
468 FsSourceType::BlockDevice(block_client_end),
469 false,
470 &fuchsia_inspect::Inspector::default(),
471 )
472 .expect("failed to parse the vmo");
473 let root = vfs::directory::serve(
474 tree,
475 vfs::execution_scope::ExecutionScope::new(),
476 fio::PERM_READABLE | fio::PERM_WRITABLE,
477 );
478 let file = open_file(&root, "file1", fio::PERM_READABLE | fio::PERM_WRITABLE)
479 .await
480 .expect("failed to open file");
481
482 let mut old_vmo_contents = vec![0u8; data.len()];
483 vmo_clone.read(&mut old_vmo_contents, 0).expect("failed to read from vmo clone");
484
485 let new_contents = "FILE1 CONTENTS!\n";
486 file.seek(fio::SeekOrigin::Start, 0)
487 .await
488 .expect("failed FIDL seek")
489 .map_err(zx::Status::from_raw)
490 .expect("failed to seek file");
491 write(&file, new_contents).await.expect("failed to write to file");
492
493 let mut vmo_contents_after_write = vec![0u8; data.len()];
494 vmo_clone.read(&mut vmo_contents_after_write, 0).expect("failed to read from vmo clone");
495
496 assert_eq!(
499 old_vmo_contents, vmo_contents_after_write,
500 "Data should be cached and not yet flushed to VmoBackedServer"
501 );
502
503 file.close()
505 .await
506 .expect("sync check failed")
507 .map_err(zx::Status::from_raw)
508 .expect("sync error");
509
510 let mut vmo_contents_after_sync = vec![0u8; data.len()];
511 vmo_clone.read(&mut vmo_contents_after_sync, 0).expect("failed to read from vmo clone");
512
513 assert_ne!(
515 old_vmo_contents, vmo_contents_after_sync,
516 "Data should be flushed to VmoBackedServer after sync"
517 );
518
519 root.close()
520 .await
521 .expect("failed FIDL dir close")
522 .map_err(zx::Status::from_raw)
523 .expect("failed to close root");
524 }
525
526 #[fuchsia::test]
527 async fn test_metrics_of_fs_with_multiple_files() {
528 let server = VmoBackedServer::from_file(BLOCK_SIZE, "/pkg/data/nest.img");
529
530 let (block_client_end, block_server_end) =
531 fidl::endpoints::create_endpoints::<fblock::BlockMarker>();
532 std::thread::spawn(move || {
533 let mut executor = fasync::TestExecutor::new();
534 let _task = executor.run_singlethreaded(server.serve(block_server_end.into_stream()));
535 });
536
537 let inspector = fuchsia_inspect::Inspector::default();
538 let tree = construct_fs(
539 FsSourceType::BlockDevice(block_client_end),
540 false,
541 &inspector,
542 )
543 .expect("failed to parse the vmo");
544 let root = vfs::directory::serve(
545 tree,
546 vfs::execution_scope::ExecutionScope::new(),
547 fio::PERM_READABLE | fio::PERM_WRITABLE,
548 );
549
550 let file1 = open_file(
551 &root,
552 "file1",
553 fio::PERM_READABLE | fio::PERM_WRITABLE | fio::Flags::FILE_TRUNCATE,
554 )
555 .await
556 .expect("open with truncate should succeed");
557 let contents = read_to_string(&file1).await.expect("failed to read file");
558 assert_eq!(contents, "");
559 diagnostics_assertions::assert_data_tree!(inspector, root: {
560 file_metrics: {
561 num_open_requests: 1u64,
562 num_read_requests: 1u64,
563 num_truncate_requests: 1u64,
564 num_write_requests: 0u64,
565 num_writes_past_eof_attempts: 0u64,
566 num_successful_overwrites: 0u64,
567 num_blocks_overwritten: 0u64,
568 }
569 });
570 file1
571 .seek(fio::SeekOrigin::Start, 0)
572 .await
573 .expect("failed to seek")
574 .map_err(zx::Status::from_raw)
575 .expect("seek error");
576 write(&file1, "FILE1 CONTENTS!\n").await.expect("failed to write to file");
577 file1
578 .seek(fio::SeekOrigin::Start, 0)
579 .await
580 .expect("failed to seek")
581 .map_err(zx::Status::from_raw)
582 .expect("seek error");
583 let new_contents = read_to_string(&file1).await.expect("failed to read file");
586 assert_eq!(new_contents, "FILE1 CONTENTS!\n");
587 diagnostics_assertions::assert_data_tree!(inspector, root: {
588 file_metrics: {
589 num_open_requests: 1u64,
590 num_read_requests: 3u64,
591 num_truncate_requests: 1u64,
592 num_write_requests: 1u64,
593 num_writes_past_eof_attempts: 0u64,
594 num_successful_overwrites: 1u64,
595 num_blocks_overwritten: 1u64,
596 }
597 });
598
599 let file2 = open_file(&root, "inner/file2", fio::PERM_READABLE | fio::PERM_WRITABLE)
602 .await
603 .expect("failed to open inner/file2");
604 let _contents = read_to_string(&file2).await.expect("failed to read file2");
605
606 diagnostics_assertions::assert_data_tree!(inspector, root: {
607 file_metrics: {
608 num_open_requests: 2u64,
609 num_read_requests: 5u64,
610 num_truncate_requests: 1u64,
611 num_write_requests: 1u64,
612 num_writes_past_eof_attempts: 0u64,
613 num_successful_overwrites: 1u64,
614 num_blocks_overwritten: 1u64,
615 }
616 });
617
618 root.close()
619 .await
620 .expect("failed FIDL dir close")
621 .map_err(zx::Status::from_raw)
622 .expect("failed to close root");
623 }
624
625 #[fuchsia::test]
626 async fn test_truncate_and_write() {
627 let server = VmoBackedServer::from_file(BLOCK_SIZE, "/pkg/data/1file.img");
628
629 let (block_client_end, block_server_end) =
630 fidl::endpoints::create_endpoints::<fblock::BlockMarker>();
631 std::thread::spawn(move || {
632 let mut executor = fasync::TestExecutor::new();
633 let _task = executor.run_singlethreaded(server.serve(block_server_end.into_stream()));
634 });
635
636 let inspector = fuchsia_inspect::Inspector::default();
637 let tree = construct_fs(
638 FsSourceType::BlockDevice(block_client_end),
639 false,
640 &inspector,
641 )
642 .expect("failed to parse the vmo");
643 let root = vfs::directory::serve(
644 tree,
645 vfs::execution_scope::ExecutionScope::new(),
646 fio::PERM_READABLE | fio::PERM_WRITABLE,
647 );
648
649 let file = open_file(&root, "file1", fio::PERM_READABLE).await.expect("open failed");
651 let original_contents = read_to_string(&file).await.expect("read failed");
652 assert_eq!(original_contents, "file1 contents.\n");
653 file.close().await.unwrap().map_err(zx::Status::from_raw).unwrap();
654
655 let file = open_file(
657 &root,
658 "file1",
659 fio::PERM_READABLE | fio::PERM_WRITABLE | fio::Flags::FILE_TRUNCATE,
660 )
661 .await
662 .expect("open with truncate failed");
663 assert_eq!(read_to_string(&file).await.expect("read failed"), "");
664
665 let new_content = "FILE1 CONTENTS.\n";
667 write(&file, new_content).await.expect("write failed");
668 file.seek(fio::SeekOrigin::Start, 0)
669 .await
670 .expect("seek failed")
671 .map_err(zx::Status::from_raw)
672 .expect("seek error");
673 assert_eq!(read_to_string(&file).await.expect("read failed"), new_content);
674
675 let huge_content = vec![1u8; 50];
677 let error =
678 write(&file, &huge_content).await.expect_err("write past allocated size should fail");
679 match error {
680 WriteError::WriteError(status) => assert_eq!(status, zx::Status::NOT_SUPPORTED),
681 _ => panic!("Unexpected error: {:?}", error),
682 }
683
684 let partial_content = vec![1u8; 2];
686 let error = write(&file, &partial_content)
687 .await
688 .expect_err("write past allocated size should fail");
689 match error {
690 WriteError::WriteError(status) => assert_eq!(status, zx::Status::NOT_SUPPORTED),
691 _ => panic!("Unexpected error: {:?}", error),
692 }
693
694 file.seek(fio::SeekOrigin::Start, 0)
696 .await
697 .expect("seek failed")
698 .map_err(zx::Status::from_raw)
699 .expect("seek error");
700 assert_eq!(read_to_string(&file).await.expect("read failed"), new_content);
701
702 file.close().await.unwrap().map_err(zx::Status::from_raw).unwrap();
703 root.close().await.unwrap().map_err(zx::Status::from_raw).unwrap();
704 }
705
706 #[fuchsia::test]
707 async fn test_symlink() {
708 let root_dir = ExtDirectory::new(1, ExtAttributes::default(), XattrMap::default());
709 let symlink_target = b"target/path";
710 let symlink_node = ExtSymlink::new(
711 2,
712 ExtAttributes::default(),
713 XattrMap::default(),
714 symlink_target.to_vec(),
715 );
716
717 root_dir.insert_child("my_symlink", symlink_node).unwrap();
718
719 let root = vfs::directory::serve(
720 root_dir,
721 vfs::execution_scope::ExecutionScope::new(),
722 fio::PERM_READABLE,
723 );
724
725 let expected =
727 vec![DirEntry { name: String::from("my_symlink"), kind: DirentKind::Symlink }];
728 assert_eq!(readdir(&root).await.unwrap(), expected);
729
730 let (symlink_proxy, server_end) = fidl::endpoints::create_proxy::<fio::SymlinkMarker>();
732 root.open(
733 "my_symlink",
734 fio::Flags::PROTOCOL_SYMLINK | fio::PERM_READABLE,
735 &fio::Options::default(),
736 server_end.into_channel().into(),
737 )
738 .expect("open symlink failed");
739
740 let target_bytes = symlink_proxy.describe().await.expect("describe failed").target.unwrap();
741 assert_eq!(target_bytes, symlink_target);
742
743 let (node_proxy, server_end) = fidl::endpoints::create_proxy::<fio::NodeMarker>();
746 root.open(
747 "my_symlink/child",
748 fio::Flags::empty(),
749 &fio::Options::default(),
750 server_end.into_channel().into(),
751 )
752 .expect("open path through symlink failed");
753
754 let mut event_stream = node_proxy.take_event_stream();
755 let event = event_stream.next().await.unwrap().expect_err("expected closed channel error");
756 match event {
757 fidl::Error::ClientChannelClosed { status, .. } => assert_eq!(status, Status::NOT_DIR),
758 other => panic!("Unexpected event error: {:?}", other),
759 }
760
761 symlink_proxy.close().await.unwrap().map_err(zx::Status::from_raw).unwrap();
762 root.close().await.unwrap().map_err(zx::Status::from_raw).unwrap();
763 }
764
765 #[fuchsia::test]
766 async fn test_symlink_from_image() {
767 let data = fs::read("/pkg/data/symlink.img").expect("Unable to read file");
768 let vmo = Vmo::create(data.len() as u64).expect("VMO is created");
769 vmo.write(data.as_slice(), 0).expect("VMO write() succeeds");
770 let buffer = FsSourceType::Vmo(vmo);
771
772 let tree = construct_fs(buffer, true, &fuchsia_inspect::Inspector::default())
773 .expect("construct_fs parses the vmo");
774 let root = vfs::directory::serve(
775 tree,
776 vfs::execution_scope::ExecutionScope::new(),
777 fio::PERM_READABLE,
778 );
779
780 let expected = vec![
781 DirEntry { name: String::from("file1"), kind: DirentKind::File },
782 DirEntry { name: String::from("lost+found"), kind: DirentKind::Directory },
783 DirEntry { name: String::from("symlink1"), kind: DirentKind::Symlink },
784 ];
785 let mut actual = readdir(&root).await.unwrap();
786 actual.sort_by_key(|e| e.name.clone());
787 assert_eq!(actual, expected);
788
789 let (symlink_proxy, server_end) = fidl::endpoints::create_proxy::<fio::SymlinkMarker>();
790 root.open(
791 "symlink1",
792 fio::Flags::PROTOCOL_SYMLINK | fio::PERM_READABLE,
793 &fio::Options::default(),
794 server_end.into_channel().into(),
795 )
796 .expect("open symlink failed");
797
798 let target_bytes = symlink_proxy.describe().await.expect("describe failed").target.unwrap();
799 assert_eq!(target_bytes, b"file1");
800
801 symlink_proxy.close().await.unwrap().map_err(zx::Status::from_raw).unwrap();
802 root.close().await.unwrap().map_err(zx::Status::from_raw).unwrap();
803 }
804}