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::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                // Too small to even fit the first copy of the ext4 Super Block.
58                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                // TODO(https://fxbug.dev/42073143): Handle other types.
114            }
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        // Create a device that is Ext4 formatted.
268        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        // Write to the allocated extent of this file.
280        let tree = construct_fs(
281            FsSourceType::BlockDevice(block_client_end1),
282            /* read_only= */ 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        // Construct Ext4 fs again, and verify that the written data is still there.
316        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            /* read_only= */ 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        // Write to the allocated extent of this file.
370        let tree = construct_fs(
371            FsSourceType::BlockDevice(block_client_end1),
372            /* read_only= */ 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        // There is not enough allocated bytes in this file to write this new content.
387        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        // Construct Ext4 fs again, and verify that the written data is still there.
413        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            /* read_only= */ 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        // Clone VMO to observe underlying changes made by the server.
454        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        // Write to the allocated extent of this file.
467        let tree = construct_fs(
468            FsSourceType::BlockDevice(block_client_end),
469            /* read_only= */ 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        // The write is stored in the device cache but has not yet been been flushed to the
497        // underlying VMO.
498        assert_eq!(
499            old_vmo_contents, vmo_contents_after_write,
500            "Data should be cached and not yet flushed to VmoBackedServer"
501        );
502
503        // Closing the file will call sync to flush the contents to the backing VMO.
504        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        // Data should now be flushed to underlying VMO.
514        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            /* read_only= */ 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        // `read_to_string` loops read until no bytes are read back. So for non-empty strings, we
584        // expect to see two more read requests.
585        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        // Perform opens and reads on another file. Should see them reflected in the inspector
600        // metrics.
601        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            /* read_only= */ 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        // Check original contents
650        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        // Open with TRUNCATE, reading from this should return empty string.
656        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        // Write to the file and verify we see new contents.
666        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        // Check that writing past original file size fails.
676        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        // Check that overwriting the file partially is not supported.
685        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        // We see the content written previously.
695        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        // Verify readdir lists the symlink with DirentKind::Symlink
726        let expected =
727            vec![DirEntry { name: String::from("my_symlink"), kind: DirentKind::Symlink }];
728        assert_eq!(readdir(&root).await.unwrap(), expected);
729
730        // Verify we can open the symlink and read its target
731        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        // Verify that trying to traverse through the symlink fails with
744        // NOT_DIR.
745        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}