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::types::ExtAttributes;
8use ext4_lib::processor::Ext4Processor;
9use ext4_lib::readers::{BlockDeviceReader, ReaderWriter, VmoReader};
10use ext4_lib::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
40// Default is to create read-only fs
41pub fn construct_fs(
42    source: FsSourceType,
43    inspector: &fuchsia_inspect::Inspector,
44) -> Result<Arc<ExtDirectory>, ConstructFsError> {
45    // TODO(https://fxbug.dev/479943428): Enable creating writeable fs when this is fully supported.
46    construct_fs_internal(source, true, inspector)
47}
48
49fn construct_fs_internal(
50    source: FsSourceType,
51    read_only: bool,
52    inspector: &fuchsia_inspect::Inspector,
53) -> Result<Arc<ExtDirectory>, ConstructFsError> {
54    let reader: Arc<dyn ReaderWriter> = match source {
55        FsSourceType::BlockDevice(block_device) => {
56            Arc::new(BlockDeviceReader::from_client_end(block_device).map_err(|e| {
57                error!("Error constructing file system: {}", e);
58                ConstructFsError::VmoReadError(zx::Status::IO_INVALID)
59            })?)
60        }
61        FsSourceType::Vmo(vmo) => {
62            let size = vmo.get_size().map_err(ConstructFsError::VmoReadError)?;
63            if size < MIN_EXT4_SIZE as u64 {
64                // Too small to even fit the first copy of the ext4 Super Block.
65                return Err(ConstructFsError::VmoReadError(zx::Status::NO_SPACE));
66            }
67
68            Arc::new(VmoReader::new(Arc::new(vmo)))
69        }
70    };
71    let processor = Arc::new(Ext4Processor::new(reader, read_only));
72    let dir = build_fs_dir(processor.clone(), structs::ROOT_INODE_NUM, read_only)?;
73    processor.record_statistics(inspector.root());
74    Ok(dir)
75}
76
77fn build_fs_dir(
78    processor: Arc<Ext4Processor>,
79    ino: u32,
80    read_only: bool,
81) -> Result<Arc<ExtDirectory>, ConstructFsError> {
82    let inode = processor.inode(ino)?;
83    let entries = processor.entries_from_inode(&inode)?;
84    let attributes = ExtAttributes::from_inode(inode);
85    let xattrs = processor.inode_xattrs(ino)?;
86    let dir = ExtDirectory::new(ino as u64, attributes, xattrs);
87
88    for entry in entries {
89        let entry_name = entry.name()?;
90        if entry_name == "." || entry_name == ".." {
91            continue;
92        }
93
94        let entry_ino = u32::from(entry.e2d_ino);
95        match EntryType::from_u8(entry.e2d_type)? {
96            EntryType::Directory => {
97                dir.insert_child(
98                    entry_name,
99                    build_fs_dir(processor.clone(), entry_ino, read_only)?,
100                )
101                .map_err(ConstructFsError::NodeError)?;
102            }
103            EntryType::RegularFile => {
104                dir.insert_child(
105                    entry_name,
106                    ExtFile::from_processor(processor.clone(), entry_ino, read_only)
107                        .map_err(ConstructFsError::NodeError)?,
108                )
109                .map_err(ConstructFsError::NodeError)?;
110            }
111            _ => {
112                // TODO(https://fxbug.dev/42073143): Handle other types.
113            }
114        }
115    }
116
117    Ok(dir)
118}
119
120#[cfg(test)]
121mod tests {
122    use super::{FsSourceType, construct_fs, construct_fs_internal};
123
124    use ext4_lib::structs::MIN_EXT4_SIZE;
125    use fuchsia_fs::directory::{DirEntry, DirentKind, open_file, open_node, readdir};
126    use fuchsia_fs::file::{WriteError, read_to_string, write};
127    use std::fs;
128    use std::sync::Arc;
129    use vmo_backed_block_server::{InitialContents, VmoBackedServerOptions};
130    use zx::{HandleBased, Status, Vmo};
131    use {fidl_fuchsia_io as fio, fidl_fuchsia_storage_block as fblock, fuchsia_async as fasync};
132
133    #[fuchsia::test]
134    fn image_too_small() {
135        let vmo = Vmo::create(10).expect("VMO is created");
136        vmo.write(b"too small", 0).expect("VMO write() succeeds");
137        let buffer = FsSourceType::Vmo(vmo);
138
139        assert!(
140            construct_fs(buffer, &fuchsia_inspect::Inspector::default()).is_err(),
141            "Expected failed parsing of VMO."
142        );
143    }
144
145    #[fuchsia::test]
146    fn invalid_fs() {
147        let vmo = Vmo::create(MIN_EXT4_SIZE as u64).expect("VMO is created");
148        vmo.write(b"not ext4", 0).expect("VMO write() succeeds");
149        let buffer = FsSourceType::Vmo(vmo);
150
151        assert!(
152            construct_fs(buffer, &fuchsia_inspect::Inspector::default()).is_err(),
153            "Expected failed parsing of VMO."
154        );
155    }
156
157    #[fuchsia::test]
158    async fn list_root() {
159        let data = fs::read("/pkg/data/nest.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, &fuchsia_inspect::Inspector::default())
165            .expect("construct_fs parses the vmo");
166        let root = vfs::directory::serve(tree, fio::PERM_READABLE);
167
168        let expected = vec![
169            DirEntry { name: String::from("file1"), kind: DirentKind::File },
170            DirEntry { name: String::from("inner"), kind: DirentKind::Directory },
171            DirEntry { name: String::from("lost+found"), kind: DirentKind::Directory },
172        ];
173        assert_eq!(readdir(&root).await.unwrap(), expected);
174
175        let file = open_file(&root, "file1", fio::PERM_READABLE).await.unwrap();
176        assert_eq!(read_to_string(&file).await.unwrap(), "file1 contents.\n");
177        file.close().await.unwrap().map_err(zx::Status::from_raw).unwrap();
178        root.close().await.unwrap().map_err(zx::Status::from_raw).unwrap();
179    }
180
181    #[fuchsia::test]
182    async fn get_dac_attributes() {
183        let data = fs::read("/pkg/data/dac_attributes.img").expect("Unable to read file");
184        let vmo = Vmo::create(data.len() as u64).expect("VMO is created");
185        vmo.write(data.as_slice(), 0).expect("VMO write() succeeds");
186        let buffer = FsSourceType::Vmo(vmo);
187
188        let tree = construct_fs(buffer, &fuchsia_inspect::Inspector::default())
189            .expect("construct_fs parses the VMO");
190        let root = vfs::directory::serve(tree, fio::PERM_READABLE);
191
192        let expected_entries = vec![
193            DirEntry { name: String::from("dir_1000"), kind: DirentKind::Directory },
194            DirEntry { name: String::from("dir_root"), kind: DirentKind::Directory },
195            DirEntry { name: String::from("file_1000"), kind: DirentKind::File },
196            DirEntry { name: String::from("file_root"), kind: DirentKind::File },
197            DirEntry { name: String::from("lost+found"), kind: DirentKind::Directory },
198        ];
199        assert_eq!(readdir(&root).await.unwrap(), expected_entries);
200
201        #[derive(Debug, PartialEq)]
202        struct Node {
203            name: String,
204            mode: u32,
205            uid: u32,
206            gid: u32,
207        }
208
209        let expected_attributes = vec![
210            Node { name: String::from("dir_1000"), mode: 0x416D, uid: 1000, gid: 1000 },
211            Node { name: String::from("dir_root"), mode: 0x4140, uid: 0, gid: 0 },
212            Node { name: String::from("file_1000"), mode: 0x8124, uid: 1000, gid: 1000 },
213            Node { name: String::from("file_root"), mode: 0x8100, uid: 0, gid: 0 },
214        ];
215
216        let attributes_query = fio::NodeAttributesQuery::MODE
217            | fio::NodeAttributesQuery::UID
218            | fio::NodeAttributesQuery::GID;
219        for expected_node in &expected_attributes {
220            let node_proxy = open_node(&root, expected_node.name.as_str(), fio::PERM_READABLE)
221                .await
222                .expect("node open failed");
223            let (mut_attrs, _immut_attrs) = node_proxy
224                .get_attributes(attributes_query)
225                .await
226                .expect("node get_attributes() failed")
227                .map_err(Status::from_raw)
228                .expect("node get_attributes() error");
229
230            let node = Node {
231                name: expected_node.name.clone(),
232                mode: mut_attrs.mode.expect("node attributes missing mode"),
233                uid: mut_attrs.uid.expect("node attributes missing uid"),
234                gid: mut_attrs.gid.expect("node attributes missing gid"),
235            };
236
237            node_proxy
238                .close()
239                .await
240                .expect("node close failed")
241                .map_err(Status::from_raw)
242                .expect("node close error");
243
244            assert_eq!(node, *expected_node);
245        }
246
247        root.close().await.unwrap().map_err(Status::from_raw).unwrap();
248    }
249
250    #[fuchsia::test]
251    async fn test_constructing_writeable_fs_and_writing_to_allocated_region() {
252        // Create a device that is Ext4 formatted.
253        let data = fs::read("/pkg/data/nest.img").expect("failed to read file");
254        let vmo = Vmo::create(data.len() as u64).expect("failed to create VMO");
255        vmo.write(data.as_slice(), 0).expect("failed to write to VMO");
256        let server = Arc::new(
257            VmoBackedServerOptions {
258                block_size: 512,
259                initial_contents: InitialContents::FromVmo(vmo),
260                ..Default::default()
261            }
262            .build()
263            .expect("build from VmoBackedServerOptions failed"),
264        );
265
266        let server_clone = server.clone();
267        let (block_client_end1, block_server_end1) =
268            fidl::endpoints::create_endpoints::<fblock::BlockMarker>();
269        std::thread::spawn(move || {
270            let mut executor = fasync::TestExecutor::new();
271            let _task =
272                executor.run_singlethreaded(server_clone.serve(block_server_end1.into_stream()));
273        });
274
275        // Write to the allocated extent of this file.
276        let tree = construct_fs_internal(
277            FsSourceType::BlockDevice(block_client_end1),
278            /* read_only= */ false,
279            &fuchsia_inspect::Inspector::default(),
280        )
281        .expect("failed to parse the vmo");
282        let root = vfs::directory::serve(tree, fio::PERM_READABLE | fio::PERM_WRITABLE);
283        let file = open_file(&root, "file1", fio::PERM_READABLE | fio::PERM_WRITABLE)
284            .await
285            .expect("failed to open file");
286        let original_contents = "file1 contents.\n";
287        assert_eq!(read_to_string(&file).await.expect("failed to read file"), original_contents);
288        let new_contents = "new";
289        let offset = 5;
290        file.seek(fio::SeekOrigin::Start, offset)
291            .await
292            .expect("failed FIDL seek")
293            .map_err(zx::Status::from_raw)
294            .expect("failed to seek file");
295        write(&file, new_contents).await.expect("failed to write to file");
296        file.close()
297            .await
298            .expect("failed FIDL file close")
299            .map_err(zx::Status::from_raw)
300            .expect("failed to close file");
301        root.close()
302            .await
303            .expect("failed FIDL dir close")
304            .map_err(zx::Status::from_raw)
305            .expect("failed to close root");
306
307        // Construct Ext4 fs again, and verify that the written data is still there.
308        let server_clone = server.clone();
309        let (block_client_end2, block_server_end2) =
310            fidl::endpoints::create_endpoints::<fblock::BlockMarker>();
311        std::thread::spawn(move || {
312            let mut executor = fasync::TestExecutor::new();
313            let _task =
314                executor.run_singlethreaded(server_clone.serve(block_server_end2.into_stream()));
315        });
316        let tree = construct_fs_internal(
317            FsSourceType::BlockDevice(block_client_end2),
318            /* read_only= */ true,
319            &fuchsia_inspect::Inspector::default(),
320        )
321        .expect("construct_fs parses the vmo");
322        let root = vfs::directory::serve(tree, fio::PERM_READABLE);
323        let file =
324            open_file(&root, "file1", fio::PERM_READABLE).await.expect("failed to open file");
325        let mut expected_bytes = original_contents.as_bytes().to_vec();
326        expected_bytes[offset as usize..offset as usize + new_contents.len()]
327            .copy_from_slice(new_contents.as_bytes());
328        assert_eq!(
329            read_to_string(&file).await.expect("failed to read file"),
330            String::from_utf8(expected_bytes).unwrap()
331        );
332        file.close()
333            .await
334            .expect("failed FIDL file close")
335            .map_err(zx::Status::from_raw)
336            .expect("failed to close file");
337        root.close()
338            .await
339            .expect("failed FIDL dir close")
340            .map_err(zx::Status::from_raw)
341            .expect("failed to close root");
342    }
343
344    #[fuchsia::test]
345    async fn test_writing_past_eof_fails() {
346        let data = fs::read("/pkg/data/nest.img").expect("failed to read file");
347        let vmo = Vmo::create(data.len() as u64).expect("failed to create VMO");
348        vmo.write(data.as_slice(), 0).expect("failed to write to VMO");
349        let server = Arc::new(
350            VmoBackedServerOptions {
351                block_size: 512,
352                initial_contents: InitialContents::FromVmo(vmo),
353                ..Default::default()
354            }
355            .build()
356            .expect("build from VmoBackedServerOptions failed"),
357        );
358
359        let server_clone = server.clone();
360        let (block_client_end1, block_server_end1) =
361            fidl::endpoints::create_endpoints::<fblock::BlockMarker>();
362        std::thread::spawn(move || {
363            let mut executor = fasync::TestExecutor::new();
364            let _task =
365                executor.run_singlethreaded(server_clone.serve(block_server_end1.into_stream()));
366        });
367
368        // Write to the allocated extent of this file.
369        let tree = construct_fs_internal(
370            FsSourceType::BlockDevice(block_client_end1),
371            /* read_only= */ false,
372            &fuchsia_inspect::Inspector::default(),
373        )
374        .expect("failed to parse the vmo");
375        let root = vfs::directory::serve(tree, fio::PERM_READABLE | fio::PERM_WRITABLE);
376        let file = open_file(&root, "file1", fio::PERM_READABLE | fio::PERM_WRITABLE)
377            .await
378            .expect("failed to open file");
379        let original_contents = read_to_string(&file).await.expect("failed to read file");
380
381        // There is not enough allocated bytes in this file to write this new content.
382        let new_contents = [1u8; 8192];
383        file.seek(fio::SeekOrigin::Start, 0)
384            .await
385            .expect("failed FIDL seek")
386            .map_err(zx::Status::from_raw)
387            .expect("failed to seek file");
388        let error = write(&file, &new_contents)
389            .await
390            .expect_err("write to unallocated region passed unexpectedly");
391        match error {
392            WriteError::WriteError(status) => assert_eq!(status, zx::Status::NOT_SUPPORTED),
393            _ => panic!("Unexpected error: {:?}", error),
394        }
395
396        file.close()
397            .await
398            .expect("failed FIDL file close")
399            .map_err(zx::Status::from_raw)
400            .expect("failed to close file");
401        root.close()
402            .await
403            .expect("failed FIDL dir close")
404            .map_err(zx::Status::from_raw)
405            .expect("failed to close root");
406
407        // Construct Ext4 fs again, and verify that the written data is still there.
408        let server_clone = server.clone();
409        let (block_client_end2, block_server_end2) =
410            fidl::endpoints::create_endpoints::<fblock::BlockMarker>();
411        std::thread::spawn(move || {
412            let mut executor = fasync::TestExecutor::new();
413            let _task =
414                executor.run_singlethreaded(server_clone.serve(block_server_end2.into_stream()));
415        });
416        let tree = construct_fs_internal(
417            FsSourceType::BlockDevice(block_client_end2),
418            /* read_only= */ true,
419            &fuchsia_inspect::Inspector::default(),
420        )
421        .expect("construct_fs parses the vmo");
422        let root = vfs::directory::serve(tree, fio::PERM_READABLE);
423        let file =
424            open_file(&root, "file1", fio::PERM_READABLE).await.expect("failed to open file");
425        assert_eq!(read_to_string(&file).await.expect("failed to read file"), original_contents);
426        file.close()
427            .await
428            .expect("failed FIDL file close")
429            .map_err(zx::Status::from_raw)
430            .expect("failed to close file");
431        root.close()
432            .await
433            .expect("failed FIDL dir close")
434            .map_err(zx::Status::from_raw)
435            .expect("failed to close root");
436    }
437
438    #[fuchsia::test]
439    async fn test_file_sync() {
440        let data = fs::read("/pkg/data/nest.img").expect("failed to read file");
441        let vmo = Vmo::create(data.len() as u64).expect("failed to create VMO");
442        vmo.write(data.as_slice(), 0).expect("failed to write to VMO");
443
444        // Clone VMO to observe underlying changes made by the server.
445        let vmo_clone = vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).expect("failed to clone vmo");
446
447        let server = Arc::new(
448            VmoBackedServerOptions {
449                block_size: 512,
450                initial_contents: InitialContents::FromVmo(vmo),
451                ..Default::default()
452            }
453            .build()
454            .expect("build from VmoBackedServerOptions failed"),
455        );
456
457        let server_clone = server.clone();
458        let (block_client_end, block_server_end) =
459            fidl::endpoints::create_endpoints::<fblock::BlockMarker>();
460        std::thread::spawn(move || {
461            let mut executor = fasync::TestExecutor::new();
462            let _task =
463                executor.run_singlethreaded(server_clone.serve(block_server_end.into_stream()));
464        });
465
466        // Write to the allocated extent of this file.
467        let tree = construct_fs_internal(
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(tree, fio::PERM_READABLE | fio::PERM_WRITABLE);
474        let file = open_file(&root, "file1", fio::PERM_READABLE | fio::PERM_WRITABLE)
475            .await
476            .expect("failed to open file");
477
478        let mut old_vmo_contents = vec![0u8; data.len()];
479        vmo_clone.read(&mut old_vmo_contents, 0).expect("failed to read from vmo clone");
480
481        let new_contents = "FILE1 CONTENTS!\n";
482        file.seek(fio::SeekOrigin::Start, 0)
483            .await
484            .expect("failed FIDL seek")
485            .map_err(zx::Status::from_raw)
486            .expect("failed to seek file");
487        write(&file, new_contents).await.expect("failed to write to file");
488
489        let mut vmo_contents_after_write = vec![0u8; data.len()];
490        vmo_clone.read(&mut vmo_contents_after_write, 0).expect("failed to read from vmo clone");
491
492        // The write is stored in the device cache but has not yet been been flushed to the
493        // underlying VMO.
494        assert_eq!(
495            old_vmo_contents, vmo_contents_after_write,
496            "Data should be cached and not yet flushed to VmoBackedServer"
497        );
498
499        // Closing the file will call sync to flush the contents to the backing VMO.
500        file.close()
501            .await
502            .expect("sync check failed")
503            .map_err(zx::Status::from_raw)
504            .expect("sync error");
505
506        let mut vmo_contents_after_sync = vec![0u8; data.len()];
507        vmo_clone.read(&mut vmo_contents_after_sync, 0).expect("failed to read from vmo clone");
508
509        // Data should now be flushed to underlying VMO.
510        assert_ne!(
511            old_vmo_contents, vmo_contents_after_sync,
512            "Data should be flushed to VmoBackedServer after sync"
513        );
514
515        root.close()
516            .await
517            .expect("failed FIDL dir close")
518            .map_err(zx::Status::from_raw)
519            .expect("failed to close root");
520    }
521
522    #[fuchsia::test]
523    async fn test_metrics_of_fs_with_multiple_files() {
524        let data = fs::read("/pkg/data/nest.img").expect("failed to read file");
525        let vmo = Vmo::create(data.len() as u64).expect("failed to create VMO");
526        vmo.write(data.as_slice(), 0).expect("failed to write to VMO");
527        let server = Arc::new(
528            VmoBackedServerOptions {
529                block_size: 512,
530                initial_contents: InitialContents::FromVmo(vmo),
531                ..Default::default()
532            }
533            .build()
534            .expect("build from VmoBackedServerOptions failed"),
535        );
536
537        let server_clone = server.clone();
538        let (block_client_end, block_server_end) =
539            fidl::endpoints::create_endpoints::<fblock::BlockMarker>();
540        std::thread::spawn(move || {
541            let mut executor = fasync::TestExecutor::new();
542            let _task =
543                executor.run_singlethreaded(server_clone.serve(block_server_end.into_stream()));
544        });
545
546        let inspector = fuchsia_inspect::Inspector::default();
547        let tree = construct_fs_internal(
548            FsSourceType::BlockDevice(block_client_end),
549            /* read_only= */ false,
550            &inspector,
551        )
552        .expect("failed to parse the vmo");
553        let root = vfs::directory::serve(tree, fio::PERM_READABLE | fio::PERM_WRITABLE);
554
555        let file1 = open_file(
556            &root,
557            "file1",
558            fio::PERM_READABLE | fio::PERM_WRITABLE | fio::Flags::FILE_TRUNCATE,
559        )
560        .await
561        .expect("open with truncate should succeed");
562        let contents = read_to_string(&file1).await.expect("failed to read file");
563        assert_eq!(contents, "");
564        diagnostics_assertions::assert_data_tree!(inspector, root: {
565            file_metrics: {
566                num_open_requests: 1u64,
567                num_read_requests: 1u64,
568                num_truncate_requests: 1u64,
569                num_write_requests: 0u64,
570                num_writes_past_eof_attempts: 0u64,
571                num_successful_overwrites: 0u64,
572                num_blocks_overwritten: 0u64,
573            }
574        });
575        file1
576            .seek(fio::SeekOrigin::Start, 0)
577            .await
578            .expect("failed to seek")
579            .map_err(zx::Status::from_raw)
580            .expect("seek error");
581        write(&file1, "FILE1 CONTENTS!\n").await.expect("failed to write to file");
582        file1
583            .seek(fio::SeekOrigin::Start, 0)
584            .await
585            .expect("failed to seek")
586            .map_err(zx::Status::from_raw)
587            .expect("seek error");
588        // `read_to_string` loops read until no bytes are read back. So for non-empty strings, we
589        // expect to see two more read requests.
590        let new_contents = read_to_string(&file1).await.expect("failed to read file");
591        assert_eq!(new_contents, "FILE1 CONTENTS!\n");
592        diagnostics_assertions::assert_data_tree!(inspector, root: {
593            file_metrics: {
594                num_open_requests: 1u64,
595                num_read_requests: 3u64,
596                num_truncate_requests: 1u64,
597                num_write_requests: 1u64,
598                num_writes_past_eof_attempts: 0u64,
599                num_successful_overwrites: 1u64,
600                num_blocks_overwritten: 1u64,
601            }
602        });
603
604        // Perform opens and reads on another file. Should see them reflected in the inspector
605        // metrics.
606        let file2 = open_file(&root, "inner/file2", fio::PERM_READABLE | fio::PERM_WRITABLE)
607            .await
608            .expect("failed to open inner/file2");
609        let _contents = read_to_string(&file2).await.expect("failed to read file2");
610
611        diagnostics_assertions::assert_data_tree!(inspector, root: {
612            file_metrics: {
613                num_open_requests: 2u64,
614                num_read_requests: 5u64,
615                num_truncate_requests: 1u64,
616                num_write_requests: 1u64,
617                num_writes_past_eof_attempts: 0u64,
618                num_successful_overwrites: 1u64,
619                num_blocks_overwritten: 1u64,
620            }
621        });
622
623        root.close()
624            .await
625            .expect("failed FIDL dir close")
626            .map_err(zx::Status::from_raw)
627            .expect("failed to close root");
628    }
629
630    #[fuchsia::test]
631    async fn test_truncate_and_write() {
632        let data = fs::read("/pkg/data/1file.img").expect("failed to read file");
633        let vmo = Vmo::create(data.len() as u64).expect("failed to create VMO");
634        vmo.write(data.as_slice(), 0).expect("failed to write to VMO");
635        let server = Arc::new(
636            VmoBackedServerOptions {
637                block_size: 512,
638                initial_contents: InitialContents::FromVmo(vmo),
639                ..Default::default()
640            }
641            .build()
642            .expect("build from VmoBackedServerOptions failed"),
643        );
644
645        let server_clone = server.clone();
646        let (block_client_end, block_server_end) =
647            fidl::endpoints::create_endpoints::<fblock::BlockMarker>();
648        std::thread::spawn(move || {
649            let mut executor = fasync::TestExecutor::new();
650            let _task =
651                executor.run_singlethreaded(server_clone.serve(block_server_end.into_stream()));
652        });
653
654        let inspector = fuchsia_inspect::Inspector::default();
655        let tree = construct_fs_internal(
656            FsSourceType::BlockDevice(block_client_end),
657            /* read_only= */ false,
658            &inspector,
659        )
660        .expect("failed to parse the vmo");
661        let root = vfs::directory::serve(tree, fio::PERM_READABLE | fio::PERM_WRITABLE);
662
663        // Check original contents
664        let file = open_file(&root, "file1", fio::PERM_READABLE).await.expect("open failed");
665        let original_contents = read_to_string(&file).await.expect("read failed");
666        assert_eq!(original_contents, "file1 contents.\n");
667        file.close().await.unwrap().map_err(zx::Status::from_raw).unwrap();
668
669        // Open with TRUNCATE, reading from this should return empty string.
670        let file = open_file(
671            &root,
672            "file1",
673            fio::PERM_READABLE | fio::PERM_WRITABLE | fio::Flags::FILE_TRUNCATE,
674        )
675        .await
676        .expect("open with truncate failed");
677        assert_eq!(read_to_string(&file).await.expect("read failed"), "");
678
679        // Write to the file and verify we see new contents.
680        let new_content = "FILE1 CONTENTS.\n";
681        write(&file, new_content).await.expect("write failed");
682        file.seek(fio::SeekOrigin::Start, 0)
683            .await
684            .expect("seek failed")
685            .map_err(zx::Status::from_raw)
686            .expect("seek error");
687        assert_eq!(read_to_string(&file).await.expect("read failed"), new_content);
688
689        // Check that writing past original file size fails.
690        let huge_content = vec![1u8; 50];
691        let error =
692            write(&file, &huge_content).await.expect_err("write past allocated size should fail");
693        match error {
694            WriteError::WriteError(status) => assert_eq!(status, zx::Status::NOT_SUPPORTED),
695            _ => panic!("Unexpected error: {:?}", error),
696        }
697
698        // Check that overwriting the file partially is not supported.
699        let partial_content = vec![1u8; 2];
700        let error = write(&file, &partial_content)
701            .await
702            .expect_err("write past allocated size should fail");
703        match error {
704            WriteError::WriteError(status) => assert_eq!(status, zx::Status::NOT_SUPPORTED),
705            _ => panic!("Unexpected error: {:?}", error),
706        }
707
708        // We see the content written previously.
709        file.seek(fio::SeekOrigin::Start, 0)
710            .await
711            .expect("seek failed")
712            .map_err(zx::Status::from_raw)
713            .expect("seek error");
714        assert_eq!(read_to_string(&file).await.expect("read failed"), new_content);
715
716        file.close().await.unwrap().map_err(zx::Status::from_raw).unwrap();
717        root.close().await.unwrap().map_err(zx::Status::from_raw).unwrap();
718    }
719}